Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/code_style.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Lint and Format

on:
push:
branches: [ main ]

pull_request:
branches: [ main ]
paths: [ '**/*.py' ]

workflow_dispatch:

concurrency:
group: ${{ github.workflow }}/${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
python-format:
name: Ruff Lint and Format
runs-on: ubuntu-latest

steps:
- name: Checkout Repository
uses: actions/checkout@v6

- name: Setup Ruff
uses: astral-sh/ruff-action@v3
with:
args: --version

- name: Enforce Lint
run: ruff check .

- name: Enforce Format
run: |
ruff format --diff .
Comment thread Fixed
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ evaluations) and a **standard Clenshaw-Curtis** variant (classical rule).
import numpy as np
import smolpack


def my_func(dim, x):
return np.exp(np.sum(x))


result = smolpack.int_smolyak(my_func, dim=3, qq=5)
```

Expand Down
28 changes: 18 additions & 10 deletions bin/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
import shlex
import shutil
import subprocess
import subprocess # noqa: S404
import sys
from pathlib import Path

Expand Down Expand Up @@ -33,9 +33,11 @@
logger.addHandler(stderr_handler)


def run_command(command, cwd=None):
def run_command(command: str, cwd: str | None = None) -> None:
if cwd is None:
logger.warning("No working directory specified. Using current directory.")
logger.warning(
"No working directory specified. Using current directory."
)
cwd = Path.cwd()
else:
cwd = Path(cwd)
Expand All @@ -44,7 +46,7 @@ def run_command(command, cwd=None):

logger.info(f"Executing command: '{command}' in '{cwd}'")

with subprocess.Popen(
with subprocess.Popen( # noqa: S603
shlex.split(command),
cwd=str(cwd),
stdout=subprocess.PIPE,
Expand All @@ -53,7 +55,7 @@ def run_command(command, cwd=None):
env=dict(**os.environ, PYTHONUNBUFFERED="1"),
text=True,
) as proc:
with open(log_file_path, "a") as _log_file:
with open(log_file_path, "a", encoding="utf-8") as _log_file:
for line in proc.stdout:
# _log_file.write(line)
logger.debug(line.rstrip())
Expand All @@ -67,21 +69,27 @@ def run_command(command, cwd=None):
logger.info("Command executed successfully.")


def install():
def install() -> None:
run_command("uv pip install . -v")


def wheel():
def wheel() -> None:
run_command("uv build --wheel -v")


def clean():
def clean() -> None:
logger.debug("Starting cleanup ...")

run_command("uv pip uninstall smolpack")

for entry in Path("").iterdir():
if entry.name in ["dist", "build", "lib", ".pytest_cache", ".ruff_cache"]:
if entry.name in {
"dist",
"build",
"lib",
".pytest_cache",
".ruff_cache",
}:
logger.info(f"Removing '{entry}'")
shutil.rmtree(entry)
if entry.name == "bin" and entry.is_dir():
Expand All @@ -102,7 +110,7 @@ def clean():
logger.info("Finished cleanup.")


def main():
def main() -> None:
parser = argparse.ArgumentParser(description="SMOLPACK Build Script")
parser.add_argument(
"mode",
Expand Down
4 changes: 2 additions & 2 deletions bin/get_version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3

import subprocess
import subprocess # noqa: S404
import sys
from pathlib import Path

Expand All @@ -16,7 +16,7 @@
from setuptools_scm import get_version


def main():
def main() -> None:
root = Path(__file__).parent.parent
version = get_version(root=root)
if version is None:
Expand Down
7 changes: 5 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ approximate integral as a `float`.
### Callback signature

```python
def f(dim: int, x: numpy.ndarray) -> float:
...
def f(dim: int, x: numpy.ndarray) -> float: ...
```

| Parameter | Type | Description |
Expand Down Expand Up @@ -78,9 +77,11 @@ combination technique.
import numpy as np
import smolpack


def f(dim, x):
return np.exp(np.sum(x))


result = smolpack.int_smolyak(f, dim=3, qq=5)
```

Expand All @@ -102,9 +103,11 @@ can be more accurate for extremely smooth integrands.
import numpy as np
import smolpack


def f(dim, x):
return np.exp(np.sum(x))


result = smolpack.cc_int_smolyak(f, dim=3, qq=5)
```

Expand Down
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ import smolpack
# Integrate exp(x1 + x2 + x3) over [0,1]^3
# Exact value: (e - 1)^3 ≈ 5.073214


def my_func(dim, x):
return np.exp(np.sum(x))


result = smolpack.int_smolyak(my_func, dim=3, qq=5)
print(f"Integral ≈ {result:.6f}")
```
Expand Down
20 changes: 18 additions & 2 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ $$
import numpy as np
import smolpack


def exp_sum(dim, x):
return np.exp(np.sum(x))


result = smolpack.int_smolyak(exp_sum, dim=3, qq=5)
print(f"Result = {result:.6f}")
```
Expand Down Expand Up @@ -69,9 +71,11 @@ Both solvers applied to the same integrand in 3-D:
import numpy as np
import smolpack


def exp_sum(dim, x):
return np.exp(np.sum(x))


exact = (np.e - 1.0) ** 3

r1 = smolpack.int_smolyak(exp_sum, dim=3, qq=7)
Expand All @@ -92,14 +96,18 @@ additional function evaluations.
import numpy as np
import smolpack


def exp_sum(dim, x):
return np.exp(np.sum(x))


exact = (np.e - 1.0) ** 3

for qq in range(4, 10):
result = smolpack.int_smolyak(exp_sum, dim=3, qq=qq)
print(f"qq={qq} k={qq-3} result={result:.10f} error={abs(result - exact):.2e}")
print(
f"qq={qq} k={qq - 3} result={result:.10f} error={abs(result - exact):.2e}"
)
```

## Example 5: Product integrand (separable)
Expand All @@ -111,12 +119,14 @@ $(1/2)^d$:
import numpy as np
import smolpack


def product_x(dim, x):
return np.prod(x)


dim = 5
result = smolpack.int_smolyak(product_x, dim=dim, qq=dim + 3)
exact = 0.5 ** dim
exact = 0.5**dim
print(f"Result = {result:.10f} (exact = {exact})")
```

Expand All @@ -133,9 +143,11 @@ $$
import numpy as np
import smolpack


def product_cosine(dim, x):
return np.prod(np.cos(x))


result = smolpack.int_smolyak(product_cosine, dim=10, qq=12)
exact = np.sin(1.0) ** 10
print(f"Result = {result:.10e}")
Expand All @@ -155,10 +167,12 @@ $$
import numpy as np
import smolpack


def genz_oscillatory(dim, x):
a = np.ones(dim)
return np.cos(2.0 * np.pi * a[0] + np.sum(a * x))


result = smolpack.int_smolyak(genz_oscillatory, dim=5, qq=8)
print(f"Oscillatory integral = {result:.8f}")
```
Expand All @@ -171,9 +185,11 @@ Set `print_stats=True` to see function-call and weight-evaluation counts:
import numpy as np
import smolpack


def exp_sum(dim, x):
return np.exp(np.sum(x))


result = smolpack.int_smolyak(exp_sum, dim=3, qq=5, print_stats=True)
```

Expand Down
57 changes: 57 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ issues = "https://github.com/eggzec/smolpack/issues"
dev = [
{include-group = "build"},
{include-group = "docs"},
{include-group = "lint"},
{include-group = "test"}
]
build = [
Expand All @@ -65,6 +66,9 @@ build = [
docs = [
"zensical>=0.0.23"
]
lint = [
"ruff==0.15.*",
]
test = [
"pytest>=8.3.5",
"pytest-cov>=6.0",
Expand All @@ -86,3 +90,56 @@ local_scheme = "no-local-version"

[tool.cibuildwheel]
enable = ["cpython-prerelease"]

[tool.ruff]
line-length = 80
indent-width = 4
preview = true

# Output serialization format for violations. The default serialization
# format is "full" [env: RUFF_OUTPUT_FORMAT=] [possible values:
# concise, full, json, json-lines, junit, grouped, github, gitlab,
# pylint, rdjson, azure, sarif]
output-format = "grouped"

[tool.ruff.lint]
isort.lines-after-imports = 2
isort.split-on-trailing-comma = false

select = [
"ANN", # flake8-annotations (required strict type annotations for public functions)
"S", # flake8-bandit (checks basic security issues in code)
"BLE", # flake8-blind-except (checks the except blocks that do not specify exception)
"FBT", # flake8-boolean-trap (ensure that boolean args can be used with kw only)
"E", # pycodestyle errors (PEP 8 style guide violations)
"W", # pycodestyle warnings (e.g., extra spaces, indentation issues)
"DOC", # pydoclint issues (e.g., extra or missing return, yield, warnings)
"A", # flake8-buitins (check variable and function names to not shadow builtins)
"N", # Naming convention checks (e.g., PEP 8 variable and function names)
"F", # Pyflakes errors (e.g., unused imports, undefined variables)
"I", # isort (Ensures imports are sorted properly)
"B", # flake8-bugbear (Detects likely bugs and bad practices)
"TID", # flake8-tidy-imports (Checks for banned or misplaced imports)
"UP", # pyupgrade (Automatically updates old Python syntax)
"YTT", # flake8-2020 (Detects outdated Python 2/3 compatibility issues)
"FLY", # flynt (Converts old-style string formatting to f-strings)
"PIE", # flake8-pie
"PL", # pylint
"RUF", # Ruff-specific rules (Additional optimizations and best practices)
]

ignore = []

[tool.ruff.lint.per-file-ignores]
"**/__init__.py" = ["RUF067"]
"**/tests/*.py" = [
"ANN001", # Missing type annotation for function argument
"ANN202", # Missing return type annotation for private function
"DOC201", # `return` is not documented in docstring
"PLR6301", # Method could be a function, class method, or static method
"S101", # Use of `assert` detected
]

[tool.ruff.format]
docstring-code-format = true
skip-magic-trailing-comma = true
Loading
Loading