From 15c3ebb597a95b90ab6eda4e60d6487fd4b1961b Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Fri, 8 May 2026 10:56:10 -0600 Subject: [PATCH 1/8] task: module CLI patching methods --- README.md | 78 +++++++++++++++++++++ mkl_fft/__main__.py | 67 ++++++++++++++++++ mkl_fft/_patch_startup.py | 33 +++++++++ mkl_fft/patch.py | 138 ++++++++++++++++++++++++++++++++++++++ mkl_fft/tests/test_cli.py | 92 +++++++++++++++++++++++++ mkl_fft/with_patch.py | 97 +++++++++++++++++++++++++++ 6 files changed, 505 insertions(+) create mode 100644 mkl_fft/__main__.py create mode 100644 mkl_fft/_patch_startup.py create mode 100644 mkl_fft/patch.py create mode 100644 mkl_fft/tests/test_cli.py create mode 100644 mkl_fft/with_patch.py diff --git a/README.md b/README.md index 769aed7e..fb55d38d 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,84 @@ numpy.allclose(mkl_res, np_res) # True ``` +--- +# Patching Mechanisms + +`mkl_fft` provides convenient ways to enable MKL-accelerated FFT operations in NumPy with or without modifying your code. It supports both persistent patching (applies to all Python sessions) and one-shot execution (applies only to a single command). It also supports Python functions and context managers that do the same. + +## Persistent Patching + +### Install Persistent Patch + +```bash +python -m mkl_fft patch install +``` + +### Check Patch Status + +```bash +python -m mkl_fft patch status +``` + +Checks whether the persistent patch is currently installed. Returns exit code 0 if installed, 1 if not installed. + +### Uninstall Persistent Patch + +```bash +python -m mkl_fft patch uninstall +``` + +Removes the persistent patch file, restoring NumPy to its default FFT implementation. + +## One-Shot Execution + +```bash +python -m mkl_fft with_patch [args...] +``` + +Runs a single command with MKL-accelerated FFT enabled. The patch is only active for that specific execution and does not persist. + +**Examples:** + +```bash +# Run a Python script with MKL acceleration +python -m mkl_fft with_patch python my_script.py + +# Run tests with MKL acceleration +python -m mkl_fft with_patch python -m pytest tests/ + +# Run a Python one-liner +python -m mkl_fft with_patch python -c "import numpy; print(numpy.fft.fft.__module__)" + +# Run benchmarks with MKL acceleration +python -m mkl_fft with_patch python run_benchmarks.py +``` + +## Programmatic Usage + +You can also patch NumPy programmatically in your Python code: + +```python +import mkl_fft + +# Check if currently patched +if mkl_fft.is_patched(): + print("NumPy FFT is using MKL") + +# Enable patching globally +mkl_fft.patch_numpy_fft() + +# Disable patching +mkl_fft.restore_numpy_fft() + +# Use as context manager (recommended for temporary patching) +with mkl_fft.mkl_fft(): + # NumPy FFT uses MKL inside this block + import numpy as np + result = np.fft.fft(data) +# NumPy FFT restored outside the block +``` + --- # Building from source diff --git a/mkl_fft/__main__.py b/mkl_fft/__main__.py new file mode 100644 index 00000000..a43d8656 --- /dev/null +++ b/mkl_fft/__main__.py @@ -0,0 +1,67 @@ +# Copyright (c) 2017, Intel Corporation +# +# 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 Intel Corporation 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. + +"""Command-line interface for mkl_fft.""" + +import sys + + +def main_impl(): + if len(sys.argv) < 2: + print("Usage: python -m mkl_fft [args]") + print() + print("Commands:") + print(" patch install Install persistent NumPy FFT patch") + print(" patch uninstall Uninstall persistent NumPy FFT patch") + print(" patch status Check if persistent patch is installed") + print(" with_patch Run command with temporary NumPy FFT patch") + print() + print("Examples:") + print(" python -m mkl_fft patch install") + print(" python -m mkl_fft with_patch python script.py") + sys.exit(1) + + command = sys.argv[1] + + if command == "patch": + from mkl_fft.patch import main as patch_main + + patch_main(sys.argv[2:]) + elif command == "with_patch": + from mkl_fft.with_patch import main as with_patch_main + + with_patch_main(sys.argv[2:]) + else: + print(f"Unknown command: {command}") + sys.exit(1) + + +def main(): + """Entry point for the CLI.""" + main_impl() + + +if __name__ == "__main__": + main() diff --git a/mkl_fft/_patch_startup.py b/mkl_fft/_patch_startup.py new file mode 100644 index 00000000..16e06ac4 --- /dev/null +++ b/mkl_fft/_patch_startup.py @@ -0,0 +1,33 @@ +# Copyright (c) 2017, Intel Corporation +# +# 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 Intel Corporation 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. + +"""Helper module for .pth-based persistent patching with error handling.""" + +try: + import mkl_fft + + mkl_fft.patch_numpy_fft() +except Exception: + pass diff --git a/mkl_fft/patch.py b/mkl_fft/patch.py new file mode 100644 index 00000000..7b9867ec --- /dev/null +++ b/mkl_fft/patch.py @@ -0,0 +1,138 @@ +# Copyright (c) 2017, Intel Corporation +# +# 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 Intel Corporation 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 +# 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. + +"""Persistent patch management for NumPy FFT submodule.""" + +import argparse +import site +import sys +from pathlib import Path + + +def get_pth_path(): + """Get the path to mkl_fft_patch.pth in the appropriate site-packages.""" + site_packages = site.getsitepackages() + if site_packages: + target_site = site_packages[0] + else: + target_site = site.getusersitepackages() + return Path(target_site) / "mkl_fft_patch.pth" + + +PTH_CONTENT = """import mkl_fft._patch_startup""" + + +def install_patch(): + """Install persistent NumPy FFT patch using .pth file.""" + pth_path = get_pth_path() + + if pth_path.exists(): + print(f"Persistent patch already installed at {pth_path}") + return + + try: + pth_path.parent.mkdir(parents=True, exist_ok=True) + pth_path.write_text(PTH_CONTENT) + print(f"✓ Persistent patch installed at {pth_path}") + print() + print("NumPy FFT will now use MKL-accelerated implementations in all") + print("Python sessions. To disable, run:") + print(" python -m mkl_fft patch uninstall") + except OSError as e: + print(f"Error installing patch: {e}") + print() + print("You may need to run with appropriate permissions or install to") + print("a user site-packages directory.") + sys.exit(1) + + +def uninstall_patch(): + """Uninstall persistent NumPy FFT patch.""" + pth_path = get_pth_path() + + if not pth_path.exists(): + print("No persistent patch found.") + return + + try: + pth_path.unlink() + print(f"✓ Persistent patch removed from {pth_path}") + print() + print("NumPy FFT will now use the default implementations.") + except OSError as e: + print(f"Error removing patch: {e}") + sys.exit(1) + + +def check_status(): + """Check if persistent patch is installed.""" + pth_path = get_pth_path() + + if pth_path.exists(): + print(f"✓ Persistent patch is installed at {pth_path}") + print() + print("NumPy FFT is configured to use MKL-accelerated implementations.") + return True + else: + print("✗ No persistent patch installed") + print() + print("To enable MKL-accelerated NumPy FFT globally, run:") + print(" python -m mkl_fft patch install") + return False + + +def main(args=None): + """Main entry point for patch command.""" + parser = argparse.ArgumentParser( + prog="python -m mkl_fft patch", + description="Manage persistent NumPy FFT patching with MKL acceleration", + ) + subparsers = parser.add_subparsers( + dest="command", help="Available commands" + ) + + subparsers.add_parser("install", help="Install persistent NumPy FFT patch") + subparsers.add_parser( + "uninstall", help="Uninstall persistent NumPy FFT patch" + ) + subparsers.add_parser( + "status", help="Check if persistent patch is installed" + ) + + parsed_args = parser.parse_args(args) + + if not parsed_args.command: + parser.print_help() + sys.exit(1) + + if parsed_args.command == "install": + install_patch() + elif parsed_args.command == "uninstall": + uninstall_patch() + elif parsed_args.command == "status": + sys.exit(0 if check_status() else 1) + + +if __name__ == "__main__": + main() diff --git a/mkl_fft/tests/test_cli.py b/mkl_fft/tests/test_cli.py new file mode 100644 index 00000000..31b3382c --- /dev/null +++ b/mkl_fft/tests/test_cli.py @@ -0,0 +1,92 @@ +# Copyright (c) 2017, Intel Corporation +# +# 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 Intel Corporation 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. + +import pytest + +from mkl_fft.patch import check_status, install_patch, uninstall_patch + + +@pytest.fixture +def mock_pth_path(tmp_path, monkeypatch): + """Mock the .pth file path to use a temporary directory.""" + pth_file = tmp_path / "mkl_fft_patch.pth" + + def mock_get_pth_path(): + return pth_file + + monkeypatch.setattr("mkl_fft.patch.get_pth_path", mock_get_pth_path) + return pth_file + + +def test_install_patch(mock_pth_path, capsys): + """Test installing persistent patch.""" + install_patch() + + assert mock_pth_path.exists() + content = mock_pth_path.read_text() + assert "import mkl_fft._patch_startup" in content + + captured = capsys.readouterr() + assert "Persistent patch installed" in captured.out + + +def test_install_patch_already_installed(mock_pth_path, capsys): + """Test installing patch when already installed.""" + install_patch() + install_patch() + + captured = capsys.readouterr() + assert "already installed" in captured.out + + +def test_uninstall_patch(mock_pth_path, capsys): + """Test uninstalling persistent patch.""" + install_patch() + assert mock_pth_path.exists() + + uninstall_patch() + assert not mock_pth_path.exists() + + captured = capsys.readouterr() + assert "Persistent patch removed" in captured.out + + +def test_uninstall_patch_not_installed(mock_pth_path, capsys): + """Test uninstalling patch when not installed.""" + uninstall_patch() + + captured = capsys.readouterr() + assert "No persistent patch found" in captured.out + + +def test_patch_status_check_function(mock_pth_path): + """Test check_status function return values.""" + assert not check_status() + + install_patch() + assert check_status() + + uninstall_patch() + assert not check_status() diff --git a/mkl_fft/with_patch.py b/mkl_fft/with_patch.py new file mode 100644 index 00000000..6603a220 --- /dev/null +++ b/mkl_fft/with_patch.py @@ -0,0 +1,97 @@ +# Copyright (c) 2017, Intel Corporation +# +# 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 Intel Corporation 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. + +"""Run Python commands with temporary NumPy FFT patch.""" + +import argparse +import os +import subprocess +import sys +import tempfile + + +def main(args=None): + """Run a command with mkl_fft NumPy patch enabled.""" + parser = argparse.ArgumentParser( + prog="python -m mkl_fft with_patch", + description="Run a command with temporary MKL-accelerated NumPy FFT", + usage="python -m mkl_fft with_patch [args...]", + ) + parser.add_argument( + "command", + nargs=argparse.REMAINDER, + help="Command to execute with patch enabled", + ) + + parsed_args = parser.parse_args(args) + + if not parsed_args.command: + parser.print_help() + print() + print("Examples:") + print(" python -m mkl_fft with_patch python script.py") + print(" python -m mkl_fft with_patch python -m pytest tests/") + print( + " python -m mkl_fft with_patch python -c 'import numpy; print(numpy.fft.fft.__module__)'" + ) + sys.exit(1) + + args = parsed_args.command + + sitecustomize_content = """# mkl_fft temporary patch +try: + import mkl_fft + mkl_fft.patch_numpy_fft() +except Exception: + pass +""" + + temp_dir = tempfile.mkdtemp(prefix="mkl_fft_patch_") + sitecustomize_path = os.path.join(temp_dir, "sitecustomize.py") + + try: + with open(sitecustomize_path, "w") as f: + f.write(sitecustomize_content) + + env = os.environ.copy() + + existing_pythonpath = env.get("PYTHONPATH", "") + if existing_pythonpath: + env["PYTHONPATH"] = f"{temp_dir}{os.pathsep}{existing_pythonpath}" + else: + env["PYTHONPATH"] = temp_dir + + result = subprocess.run(args, env=env) + sys.exit(result.returncode) + finally: + try: + os.unlink(sitecustomize_path) + os.rmdir(temp_dir) + except OSError: + pass + + +if __name__ == "__main__": + main() From 21e1c5467c0af7a313f3240aa61cebfde17cd91c Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Wed, 13 May 2026 12:14:57 -0600 Subject: [PATCH 2/8] fix: bad unicode --- mkl_fft/patch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mkl_fft/patch.py b/mkl_fft/patch.py index 7b9867ec..e1d60d95 100644 --- a/mkl_fft/patch.py +++ b/mkl_fft/patch.py @@ -54,7 +54,7 @@ def install_patch(): try: pth_path.parent.mkdir(parents=True, exist_ok=True) pth_path.write_text(PTH_CONTENT) - print(f"✓ Persistent patch installed at {pth_path}") + print(f"Persistent patch installed at {pth_path}") print() print("NumPy FFT will now use MKL-accelerated implementations in all") print("Python sessions. To disable, run:") @@ -77,7 +77,7 @@ def uninstall_patch(): try: pth_path.unlink() - print(f"✓ Persistent patch removed from {pth_path}") + print(f"Persistent patch removed from {pth_path}") print() print("NumPy FFT will now use the default implementations.") except OSError as e: @@ -90,12 +90,12 @@ def check_status(): pth_path = get_pth_path() if pth_path.exists(): - print(f"✓ Persistent patch is installed at {pth_path}") + print(f"Persistent patch is installed at {pth_path}") print() print("NumPy FFT is configured to use MKL-accelerated implementations.") return True else: - print("✗ No persistent patch installed") + print("No persistent patch installed") print() print("To enable MKL-accelerated NumPy FFT globally, run:") print(" python -m mkl_fft patch install") From abc566e188d48f8233ccde2e794398359d55a7de Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Wed, 13 May 2026 12:31:40 -0600 Subject: [PATCH 3/8] fix: CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7530ba..83338611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [dev] - YYYY-MM-DD ### Added +* Added CLI for persistent and ephemeral NumPy FFT patching: `python -m mkl_fft patch install/uninstall/status` for persistent patching across all Python sessions, and `python -m mkl_fft with_patch ` for one-shot execution with MKL acceleration ### Changed * Removed `numpy-base` dependency and `USE_NUMPY_BASE` environment variable from conda recipe [gh-318](https://github.com/IntelPython/mkl_fft/pull/318) From c8e43a04c4a10d0e935725b907fab905c2d02b39 Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Thu, 21 May 2026 16:02:08 -0600 Subject: [PATCH 4/8] fix: review --- mkl_fft/__main__.py | 78 +++++++++++++++++++----------- mkl_fft/_patch_startup.py | 2 +- mkl_fft/patch.py | 99 ++++++++++++++++++--------------------- mkl_fft/tests/test_cli.py | 2 +- mkl_fft/with_patch.py | 29 ++++-------- 5 files changed, 107 insertions(+), 103 deletions(-) diff --git a/mkl_fft/__main__.py b/mkl_fft/__main__.py index a43d8656..ecc47d65 100644 --- a/mkl_fft/__main__.py +++ b/mkl_fft/__main__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017, Intel Corporation +# Copyright (c) 2026, Intel Corporation # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -25,42 +25,66 @@ """Command-line interface for mkl_fft.""" +import argparse import sys -def main_impl(): - if len(sys.argv) < 2: - print("Usage: python -m mkl_fft [args]") - print() - print("Commands:") - print(" patch install Install persistent NumPy FFT patch") - print(" patch uninstall Uninstall persistent NumPy FFT patch") - print(" patch status Check if persistent patch is installed") - print(" with_patch Run command with temporary NumPy FFT patch") - print() - print("Examples:") - print(" python -m mkl_fft patch install") - print(" python -m mkl_fft with_patch python script.py") - sys.exit(1) +def main(): + """Entry point for the CLI.""" + parser = argparse.ArgumentParser( + prog="python -m mkl_fft", + description="MKL-accelerated FFT for NumPy", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="Enable verbose output" + ) + subparsers = parser.add_subparsers(dest="command", help="Available commands") - command = sys.argv[1] + # patch subcommand with its own subparsers + patch_parser = subparsers.add_parser( + "patch", help="Manage persistent NumPy FFT patching" + ) + patch_subparsers = patch_parser.add_subparsers( + dest="patch_command", help="Patch operations" + ) + patch_subparsers.add_parser("install", help="Install persistent NumPy FFT patch") + patch_subparsers.add_parser( + "uninstall", help="Uninstall persistent NumPy FFT patch" + ) + patch_subparsers.add_parser("status", help="Check if persistent patch is installed") - if command == "patch": - from mkl_fft.patch import main as patch_main + # with_patch subcommand + with_patch_parser = subparsers.add_parser( + "with_patch", help="Run command with temporary NumPy FFT patch" + ) + with_patch_parser.add_argument( + "command", nargs=argparse.REMAINDER, help="Command to execute with patch" + ) - patch_main(sys.argv[2:]) - elif command == "with_patch": - from mkl_fft.with_patch import main as with_patch_main + args = parser.parse_args() - with_patch_main(sys.argv[2:]) - else: - print(f"Unknown command: {command}") + if not args.command: + parser.print_help() sys.exit(1) + if args.command == "patch": + from mkl_fft.patch import check_status, install_patch, uninstall_patch -def main(): - """Entry point for the CLI.""" - main_impl() + if not args.patch_command: + patch_parser.print_help() + sys.exit(1) + + if args.patch_command == "install": + install_patch(verbose=args.verbose) + elif args.patch_command == "uninstall": + uninstall_patch(verbose=args.verbose) + elif args.patch_command == "status": + sys.exit(0 if check_status(verbose=args.verbose) else 1) + + elif args.command == "with_patch": + from mkl_fft.with_patch import run_with_patch + + run_with_patch(args.command) if __name__ == "__main__": diff --git a/mkl_fft/_patch_startup.py b/mkl_fft/_patch_startup.py index 16e06ac4..a5ad8d5f 100644 --- a/mkl_fft/_patch_startup.py +++ b/mkl_fft/_patch_startup.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017, Intel Corporation +# Copyright (c) 2026, Intel Corporation # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: diff --git a/mkl_fft/patch.py b/mkl_fft/patch.py index e1d60d95..14b431f8 100644 --- a/mkl_fft/patch.py +++ b/mkl_fft/patch.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017, Intel Corporation +# Copyright (c) 2026, Intel Corporation # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -24,9 +24,9 @@ """Persistent patch management for NumPy FFT submodule.""" -import argparse import site import sys +import warnings from pathlib import Path @@ -43,22 +43,28 @@ def get_pth_path(): PTH_CONTENT = """import mkl_fft._patch_startup""" -def install_patch(): +def install_patch(verbose=False): """Install persistent NumPy FFT patch using .pth file.""" pth_path = get_pth_path() if pth_path.exists(): - print(f"Persistent patch already installed at {pth_path}") + if verbose: + warnings.warn( + f"Persistent patch already installed at {pth_path}", + UserWarning, + stacklevel=2 + ) return try: pth_path.parent.mkdir(parents=True, exist_ok=True) pth_path.write_text(PTH_CONTENT) - print(f"Persistent patch installed at {pth_path}") - print() - print("NumPy FFT will now use MKL-accelerated implementations in all") - print("Python sessions. To disable, run:") - print(" python -m mkl_fft patch uninstall") + if verbose: + print(f"Persistent patch installed at {pth_path}") + print() + print("NumPy FFT will now use MKL-accelerated implementations in all") + print("Python sessions. To disable, run:") + print(" python -m mkl_fft patch uninstall") except OSError as e: print(f"Error installing patch: {e}") print() @@ -67,72 +73,57 @@ def install_patch(): sys.exit(1) -def uninstall_patch(): +def uninstall_patch(verbose=False): """Uninstall persistent NumPy FFT patch.""" pth_path = get_pth_path() if not pth_path.exists(): - print("No persistent patch found.") + if verbose: + print("No persistent patch found.") return try: pth_path.unlink() - print(f"Persistent patch removed from {pth_path}") - print() - print("NumPy FFT will now use the default implementations.") + if verbose: + print(f"Persistent patch removed from {pth_path}") + print() + print("NumPy FFT will now use the default implementations.") except OSError as e: print(f"Error removing patch: {e}") sys.exit(1) -def check_status(): +def check_status(verbose=False): """Check if persistent patch is installed.""" pth_path = get_pth_path() if pth_path.exists(): - print(f"Persistent patch is installed at {pth_path}") - print() - print("NumPy FFT is configured to use MKL-accelerated implementations.") + if verbose: + print(f"Persistent patch is installed at {pth_path}") + print() + print("NumPy FFT is configured to use MKL-accelerated implementations.") return True else: - print("No persistent patch installed") - print() - print("To enable MKL-accelerated NumPy FFT globally, run:") - print(" python -m mkl_fft patch install") + if verbose: + print("No persistent patch installed") + print() + print("To enable MKL-accelerated NumPy FFT globally, run:") + print(" python -m mkl_fft patch install") return False -def main(args=None): - """Main entry point for patch command.""" - parser = argparse.ArgumentParser( - prog="python -m mkl_fft patch", - description="Manage persistent NumPy FFT patching with MKL acceleration", - ) - subparsers = parser.add_subparsers( - dest="command", help="Available commands" - ) - - subparsers.add_parser("install", help="Install persistent NumPy FFT patch") - subparsers.add_parser( - "uninstall", help="Uninstall persistent NumPy FFT patch" - ) - subparsers.add_parser( - "status", help="Check if persistent patch is installed" - ) - - parsed_args = parser.parse_args(args) - - if not parsed_args.command: - parser.print_help() +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python -m mkl_fft.patch ") sys.exit(1) - if parsed_args.command == "install": - install_patch() - elif parsed_args.command == "uninstall": - uninstall_patch() - elif parsed_args.command == "status": - sys.exit(0 if check_status() else 1) - - -if __name__ == "__main__": - main() + command = sys.argv[1] + if command == "install": + install_patch(verbose=True) + elif command == "uninstall": + uninstall_patch(verbose=True) + elif command == "status": + sys.exit(0 if check_status(verbose=True) else 1) + else: + print(f"Unknown command: {command}") + sys.exit(1) diff --git a/mkl_fft/tests/test_cli.py b/mkl_fft/tests/test_cli.py index 31b3382c..3cef5d04 100644 --- a/mkl_fft/tests/test_cli.py +++ b/mkl_fft/tests/test_cli.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017, Intel Corporation +# Copyright (c) 2026, Intel Corporation # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: diff --git a/mkl_fft/with_patch.py b/mkl_fft/with_patch.py index 6603a220..63bbe22b 100644 --- a/mkl_fft/with_patch.py +++ b/mkl_fft/with_patch.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017, Intel Corporation +# Copyright (c) 2026, Intel Corporation # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -25,30 +25,16 @@ """Run Python commands with temporary NumPy FFT patch.""" -import argparse import os import subprocess import sys import tempfile -def main(args=None): +def run_with_patch(args): """Run a command with mkl_fft NumPy patch enabled.""" - parser = argparse.ArgumentParser( - prog="python -m mkl_fft with_patch", - description="Run a command with temporary MKL-accelerated NumPy FFT", - usage="python -m mkl_fft with_patch [args...]", - ) - parser.add_argument( - "command", - nargs=argparse.REMAINDER, - help="Command to execute with patch enabled", - ) - - parsed_args = parser.parse_args(args) - - if not parsed_args.command: - parser.print_help() + if not args: + print("Usage: python -m mkl_fft with_patch [args...]") print() print("Examples:") print(" python -m mkl_fft with_patch python script.py") @@ -58,8 +44,6 @@ def main(args=None): ) sys.exit(1) - args = parsed_args.command - sitecustomize_content = """# mkl_fft temporary patch try: import mkl_fft @@ -93,5 +77,10 @@ def main(args=None): pass +def main(args=None): + """Deprecated entry point. Use run_with_patch() instead.""" + run_with_patch(args if args else sys.argv[1:]) + + if __name__ == "__main__": main() From 056e853f3cc7108c91f2760f25e2edc104dde73e Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Thu, 21 May 2026 16:08:28 -0600 Subject: [PATCH 5/8] review: change patch subcommand to argparse --- mkl_fft/__main__.py | 53 ++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/mkl_fft/__main__.py b/mkl_fft/__main__.py index ecc47d65..1909c798 100644 --- a/mkl_fft/__main__.py +++ b/mkl_fft/__main__.py @@ -38,53 +38,38 @@ def main(): parser.add_argument( "-v", "--verbose", action="store_true", help="Enable verbose output" ) - subparsers = parser.add_subparsers(dest="command", help="Available commands") - - # patch subcommand with its own subparsers - patch_parser = subparsers.add_parser( - "patch", help="Manage persistent NumPy FFT patching" - ) - patch_subparsers = patch_parser.add_subparsers( - dest="patch_command", help="Patch operations" - ) - patch_subparsers.add_parser("install", help="Install persistent NumPy FFT patch") - patch_subparsers.add_parser( - "uninstall", help="Uninstall persistent NumPy FFT patch" - ) - patch_subparsers.add_parser("status", help="Check if persistent patch is installed") - - # with_patch subcommand - with_patch_parser = subparsers.add_parser( - "with_patch", help="Run command with temporary NumPy FFT patch" + parser.add_argument( + "--patch", + choices=["install", "uninstall", "status"], + help="Manage persistent NumPy FFT patching", ) - with_patch_parser.add_argument( - "command", nargs=argparse.REMAINDER, help="Command to execute with patch" + parser.add_argument( + "--with-patch", + dest="with_patch", + nargs=argparse.REMAINDER, + help="Run command with temporary NumPy FFT patch", ) args = parser.parse_args() - if not args.command: - parser.print_help() - sys.exit(1) - - if args.command == "patch": + if args.patch: from mkl_fft.patch import check_status, install_patch, uninstall_patch - if not args.patch_command: - patch_parser.print_help() - sys.exit(1) - - if args.patch_command == "install": + if args.patch == "install": install_patch(verbose=args.verbose) - elif args.patch_command == "uninstall": + elif args.patch == "uninstall": uninstall_patch(verbose=args.verbose) - elif args.patch_command == "status": + elif args.patch == "status": sys.exit(0 if check_status(verbose=args.verbose) else 1) - elif args.command == "with_patch": + elif args.with_patch is not None: from mkl_fft.with_patch import run_with_patch - run_with_patch(args.command) + run_with_patch(args.with_patch) + + else: + parser.print_help() + sys.exit(1) if __name__ == "__main__": From 097b50e8fad9c321e16bcc3ceb975aa6591a8891 Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Thu, 21 May 2026 16:10:22 -0600 Subject: [PATCH 6/8] fix: with_patch needing a 2nd python/whole command --- mkl_fft/with_patch.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/mkl_fft/with_patch.py b/mkl_fft/with_patch.py index 63bbe22b..8d0e6280 100644 --- a/mkl_fft/with_patch.py +++ b/mkl_fft/with_patch.py @@ -32,18 +32,27 @@ def run_with_patch(args): - """Run a command with mkl_fft NumPy patch enabled.""" + """Run a command with mkl_fft NumPy patch enabled. + + Automatically prepends 'python' unless args start with '--'. + """ if not args: - print("Usage: python -m mkl_fft with_patch [args...]") + print("Usage: python -m mkl_fft --with-patch ") print() print("Examples:") - print(" python -m mkl_fft with_patch python script.py") - print(" python -m mkl_fft with_patch python -m pytest tests/") - print( - " python -m mkl_fft with_patch python -c 'import numpy; print(numpy.fft.fft.__module__)'" - ) + print(" python -m mkl_fft --with-patch script.py") + print(" python -m mkl_fft --with-patch -c 'import numpy; print(numpy.fft.fft.__module__)'") + print(" python -m mkl_fft --with-patch -m pytest tests/") + print(" python -m mkl_fft --with-patch -- any command here") sys.exit(1) + # If args start with '--', strip it and run as-is (arbitrary command) + if args[0] == "--": + args = args[1:] + # Otherwise, prepend 'python' for convenience + else: + args = [sys.executable] + args + sitecustomize_content = """# mkl_fft temporary patch try: import mkl_fft From 6b04cf2dc05790f4ac2e90b159acdcdc327f7b8b Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Tue, 26 May 2026 07:26:11 -0600 Subject: [PATCH 7/8] fix: moew review --- mkl_fft/__main__.py | 23 +++++++---- mkl_fft/patch.py | 39 +++++++++++------- mkl_fft/tests/test_cli.py | 86 +++++++++++++++++++++++++++++++++++---- 3 files changed, 117 insertions(+), 31 deletions(-) diff --git a/mkl_fft/__main__.py b/mkl_fft/__main__.py index 1909c798..d33a19e1 100644 --- a/mkl_fft/__main__.py +++ b/mkl_fft/__main__.py @@ -53,14 +53,23 @@ def main(): args = parser.parse_args() if args.patch: - from mkl_fft.patch import check_status, install_patch, uninstall_patch + from mkl_fft.patch import ( + PatchOperationError, + check_status, + install_patch, + uninstall_patch, + ) - if args.patch == "install": - install_patch(verbose=args.verbose) - elif args.patch == "uninstall": - uninstall_patch(verbose=args.verbose) - elif args.patch == "status": - sys.exit(0 if check_status(verbose=args.verbose) else 1) + try: + if args.patch == "install": + install_patch(verbose=args.verbose) + elif args.patch == "uninstall": + uninstall_patch(verbose=args.verbose) + elif args.patch == "status": + sys.exit(0 if check_status(verbose=args.verbose) else 1) + except PatchOperationError as exc: + print(exc, file=sys.stderr) + sys.exit(1) elif args.with_patch is not None: from mkl_fft.with_patch import run_with_patch diff --git a/mkl_fft/patch.py b/mkl_fft/patch.py index 14b431f8..4380d1c4 100644 --- a/mkl_fft/patch.py +++ b/mkl_fft/patch.py @@ -30,6 +30,10 @@ from pathlib import Path +class PatchOperationError(RuntimeError): + """Raised when a persistent patch operation cannot be completed.""" + + def get_pth_path(): """Get the path to mkl_fft_patch.pth in the appropriate site-packages.""" site_packages = site.getsitepackages() @@ -66,11 +70,11 @@ def install_patch(verbose=False): print("Python sessions. To disable, run:") print(" python -m mkl_fft patch uninstall") except OSError as e: - print(f"Error installing patch: {e}") - print() - print("You may need to run with appropriate permissions or install to") - print("a user site-packages directory.") - sys.exit(1) + raise PatchOperationError( + f"Error installing patch at {pth_path}: {e}\n\n" + "You may need to run with appropriate permissions or install to " + "a user site-packages directory." + ) from e def uninstall_patch(verbose=False): @@ -89,8 +93,9 @@ def uninstall_patch(verbose=False): print() print("NumPy FFT will now use the default implementations.") except OSError as e: - print(f"Error removing patch: {e}") - sys.exit(1) + raise PatchOperationError( + f"Error removing patch at {pth_path}: {e}" + ) from e def check_status(verbose=False): @@ -118,12 +123,16 @@ def check_status(verbose=False): sys.exit(1) command = sys.argv[1] - if command == "install": - install_patch(verbose=True) - elif command == "uninstall": - uninstall_patch(verbose=True) - elif command == "status": - sys.exit(0 if check_status(verbose=True) else 1) - else: - print(f"Unknown command: {command}") + try: + if command == "install": + install_patch(verbose=True) + elif command == "uninstall": + uninstall_patch(verbose=True) + elif command == "status": + sys.exit(0 if check_status(verbose=True) else 1) + else: + print(f"Unknown command: {command}") + sys.exit(1) + except PatchOperationError as exc: + print(exc, file=sys.stderr) sys.exit(1) diff --git a/mkl_fft/tests/test_cli.py b/mkl_fft/tests/test_cli.py index 3cef5d04..1b7d869c 100644 --- a/mkl_fft/tests/test_cli.py +++ b/mkl_fft/tests/test_cli.py @@ -23,9 +23,17 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import site + +import mkl_fft import pytest -from mkl_fft.patch import check_status, install_patch, uninstall_patch +from mkl_fft.patch import ( + PatchOperationError, + check_status, + install_patch, + uninstall_patch, +) @pytest.fixture @@ -42,7 +50,7 @@ def mock_get_pth_path(): def test_install_patch(mock_pth_path, capsys): """Test installing persistent patch.""" - install_patch() + install_patch(verbose=True) assert mock_pth_path.exists() content = mock_pth_path.read_text() @@ -52,13 +60,11 @@ def test_install_patch(mock_pth_path, capsys): assert "Persistent patch installed" in captured.out -def test_install_patch_already_installed(mock_pth_path, capsys): +def test_install_patch_already_installed(mock_pth_path): """Test installing patch when already installed.""" install_patch() - install_patch() - - captured = capsys.readouterr() - assert "already installed" in captured.out + with pytest.warns(UserWarning, match="already installed"): + install_patch(verbose=True) def test_uninstall_patch(mock_pth_path, capsys): @@ -66,7 +72,7 @@ def test_uninstall_patch(mock_pth_path, capsys): install_patch() assert mock_pth_path.exists() - uninstall_patch() + uninstall_patch(verbose=True) assert not mock_pth_path.exists() captured = capsys.readouterr() @@ -75,7 +81,7 @@ def test_uninstall_patch(mock_pth_path, capsys): def test_uninstall_patch_not_installed(mock_pth_path, capsys): """Test uninstalling patch when not installed.""" - uninstall_patch() + uninstall_patch(verbose=True) captured = capsys.readouterr() assert "No persistent patch found" in captured.out @@ -90,3 +96,65 @@ def test_patch_status_check_function(mock_pth_path): uninstall_patch() assert not check_status() + + +def test_install_patch_enables_runtime_patch_via_pth(mock_pth_path): + """Test that .pth activation results in patched NumPy FFT runtime state.""" + install_patch() + + preexisting_patch_state = mkl_fft.is_patched() + try: + site.addsitedir(str(mock_pth_path.parent)) + assert mkl_fft.is_patched() + finally: + if mkl_fft.is_patched() and not preexisting_patch_state: + mkl_fft.restore_numpy_fft() + + +def test_install_patch_raises_patch_operation_error_on_oserror( + mock_pth_path, monkeypatch +): + """Test install_patch raises a typed error for filesystem failures.""" + + def mock_write_text(*args, **kwargs): + raise OSError("mock write failure") + + monkeypatch.setattr("pathlib.Path.write_text", mock_write_text) + + with pytest.raises(PatchOperationError, match="Error installing patch"): + install_patch() + + +def test_uninstall_patch_raises_patch_operation_error_on_oserror( + mock_pth_path, monkeypatch +): + """Test uninstall_patch raises a typed error for filesystem failures.""" + install_patch() + + def mock_unlink(*args, **kwargs): + raise OSError("mock unlink failure") + + monkeypatch.setattr("pathlib.Path.unlink", mock_unlink) + + with pytest.raises(PatchOperationError, match="Error removing patch"): + uninstall_patch() + + +def test_cli_patch_install_exits_with_error_on_patch_operation_error( + monkeypatch, capsys +): + """Test CLI maps patch operation failures to exit code 1.""" + from mkl_fft import __main__ as cli_main + + def mock_install_patch(*args, **kwargs): + raise PatchOperationError("mock cli install failure") + + monkeypatch.setattr("mkl_fft.patch.install_patch", mock_install_patch) + monkeypatch.setattr("sys.argv", ["python", "--patch", "install"]) + + with pytest.raises(SystemExit) as exc_info: + cli_main.main() + + assert exc_info.value.code == 1 + captured = capsys.readouterr() + assert "mock cli install failure" in captured.err From d4ed6b9cef8a35a2fcc53c78ee33b069a2ba50e1 Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Tue, 26 May 2026 07:42:12 -0600 Subject: [PATCH 8/8] lint + review --- README.md | 69 +++++++++++++-------------------------- mkl_fft/__main__.py | 10 +++--- mkl_fft/patch.py | 10 ++++-- mkl_fft/tests/test_cli.py | 2 +- mkl_fft/with_patch.py | 18 +++++----- 5 files changed, 46 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index fb55d38d..b1bc6d42 100644 --- a/README.md +++ b/README.md @@ -84,79 +84,56 @@ numpy.allclose(mkl_res, np_res) --- # Patching Mechanisms -`mkl_fft` provides convenient ways to enable MKL-accelerated FFT operations in NumPy with or without modifying your code. It supports both persistent patching (applies to all Python sessions) and one-shot execution (applies only to a single command). It also supports Python functions and context managers that do the same. +`mkl_fft` provides convenient patch methods to enable MKL-accelerated FFT operations in NumPy with or without modifying your code. -## Persistent Patching +## CLI Quickstart -### Install Persistent Patch +### Persistent patch (all Python sessions) ```bash -python -m mkl_fft patch install -``` - -### Check Patch Status - -```bash -python -m mkl_fft patch status -``` - -Checks whether the persistent patch is currently installed. Returns exit code 0 if installed, 1 if not installed. +# Install +python -m mkl_fft --patch install -### Uninstall Persistent Patch +# Status (exit code: 0 = installed, 1 = not installed) +python -m mkl_fft --patch status -```bash -python -m mkl_fft patch uninstall +# Remove +python -m mkl_fft --patch uninstall ``` -Removes the persistent patch file, restoring NumPy to its default FFT implementation. - -## One-Shot Execution +### Verify current FFT backend ```bash -python -m mkl_fft with_patch [args...] +python -c "import numpy; print(f'numpy.fft.fft.__module__: {numpy.fft.fft.__module__}')" ``` -Runs a single command with MKL-accelerated FFT enabled. The patch is only active for that specific execution and does not persist. - -**Examples:** +### One-shot patch (single command only) ```bash -# Run a Python script with MKL acceleration -python -m mkl_fft with_patch python my_script.py +# Script +python -m mkl_fft --with-numpy-patch my_script.py -# Run tests with MKL acceleration -python -m mkl_fft with_patch python -m pytest tests/ +# Pytest +python -m mkl_fft --with-numpy-patch -m pytest tests/ -# Run a Python one-liner -python -m mkl_fft with_patch python -c "import numpy; print(numpy.fft.fft.__module__)" +# One-liner +python -m mkl_fft --with-numpy-patch -c "import numpy; print(f\"numpy.fft.fft.__module__: {numpy.fft.fft.__module__}\")" -# Run benchmarks with MKL acceleration -python -m mkl_fft with_patch python run_benchmarks.py +# Non-Python command +python -m mkl_fft --with-numpy-patch -- [args...] ``` -## Programmatic Usage - -You can also patch NumPy programmatically in your Python code: +## Programmatic Quickstart ```python import mkl_fft -# Check if currently patched -if mkl_fft.is_patched(): - print("NumPy FFT is using MKL") - -# Enable patching globally mkl_fft.patch_numpy_fft() - -# Disable patching +print(mkl_fft.is_patched()) mkl_fft.restore_numpy_fft() -# Use as context manager (recommended for temporary patching) with mkl_fft.mkl_fft(): - # NumPy FFT uses MKL inside this block - import numpy as np - result = np.fft.fft(data) -# NumPy FFT restored outside the block + pass ``` --- diff --git a/mkl_fft/__main__.py b/mkl_fft/__main__.py index d33a19e1..8a174b2c 100644 --- a/mkl_fft/__main__.py +++ b/mkl_fft/__main__.py @@ -44,8 +44,8 @@ def main(): help="Manage persistent NumPy FFT patching", ) parser.add_argument( - "--with-patch", - dest="with_patch", + "--with-numpy-patch", + dest="with_numpy_patch", nargs=argparse.REMAINDER, help="Run command with temporary NumPy FFT patch", ) @@ -71,10 +71,10 @@ def main(): print(exc, file=sys.stderr) sys.exit(1) - elif args.with_patch is not None: - from mkl_fft.with_patch import run_with_patch + elif args.with_numpy_patch is not None: + from mkl_fft.with_patch import run_with_numpy_patch - run_with_patch(args.with_patch) + run_with_numpy_patch(args.with_numpy_patch) else: parser.print_help() diff --git a/mkl_fft/patch.py b/mkl_fft/patch.py index 4380d1c4..f5967548 100644 --- a/mkl_fft/patch.py +++ b/mkl_fft/patch.py @@ -56,7 +56,7 @@ def install_patch(verbose=False): warnings.warn( f"Persistent patch already installed at {pth_path}", UserWarning, - stacklevel=2 + stacklevel=2, ) return @@ -66,7 +66,9 @@ def install_patch(verbose=False): if verbose: print(f"Persistent patch installed at {pth_path}") print() - print("NumPy FFT will now use MKL-accelerated implementations in all") + print( + "NumPy FFT will now use MKL-accelerated implementations in all" + ) print("Python sessions. To disable, run:") print(" python -m mkl_fft patch uninstall") except OSError as e: @@ -106,7 +108,9 @@ def check_status(verbose=False): if verbose: print(f"Persistent patch is installed at {pth_path}") print() - print("NumPy FFT is configured to use MKL-accelerated implementations.") + print( + "NumPy FFT is configured to use MKL-accelerated implementations." + ) return True else: if verbose: diff --git a/mkl_fft/tests/test_cli.py b/mkl_fft/tests/test_cli.py index 1b7d869c..d195663d 100644 --- a/mkl_fft/tests/test_cli.py +++ b/mkl_fft/tests/test_cli.py @@ -25,9 +25,9 @@ import site -import mkl_fft import pytest +import mkl_fft from mkl_fft.patch import ( PatchOperationError, check_status, diff --git a/mkl_fft/with_patch.py b/mkl_fft/with_patch.py index 8d0e6280..70fbd7e3 100644 --- a/mkl_fft/with_patch.py +++ b/mkl_fft/with_patch.py @@ -31,19 +31,21 @@ import tempfile -def run_with_patch(args): +def run_with_numpy_patch(args): """Run a command with mkl_fft NumPy patch enabled. Automatically prepends 'python' unless args start with '--'. """ if not args: - print("Usage: python -m mkl_fft --with-patch ") + print("Usage: python -m mkl_fft --with-numpy-patch ") print() print("Examples:") - print(" python -m mkl_fft --with-patch script.py") - print(" python -m mkl_fft --with-patch -c 'import numpy; print(numpy.fft.fft.__module__)'") - print(" python -m mkl_fft --with-patch -m pytest tests/") - print(" python -m mkl_fft --with-patch -- any command here") + print(" python -m mkl_fft --with-numpy-patch script.py") + print( + " python -m mkl_fft --with-numpy-patch -c 'import numpy; print(numpy.fft.fft.__module__)'" + ) + print(" python -m mkl_fft --with-numpy-patch -m pytest tests/") + print(" python -m mkl_fft --with-numpy-patch -- any command here") sys.exit(1) # If args start with '--', strip it and run as-is (arbitrary command) @@ -87,8 +89,8 @@ def run_with_patch(args): def main(args=None): - """Deprecated entry point. Use run_with_patch() instead.""" - run_with_patch(args if args else sys.argv[1:]) + """Deprecated entry point. Use run_with_numpy_patch() instead.""" + run_with_numpy_patch(args if args else sys.argv[1:]) if __name__ == "__main__":