From e514926c6dff1a3432a4aa08b59f402527e30538 Mon Sep 17 00:00:00 2001 From: Cameron Urban Date: Wed, 13 May 2026 15:11:06 -0400 Subject: [PATCH 01/14] Add PyCharm setup guide and tidy docs Switch the DOI and license badges in README from dynamic to static shields.io equivalents because the dynamic versions often fail to render or update. --- .github/pull_request_template.md | 4 ++-- CONTRIBUTING.md | 36 ++++++++++++++++++++++++++++++++ README.md | 6 +++--- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5bf64e4f..a5656881 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,7 +12,7 @@ Link any related issues using GitHub's syntax. For bugs, write, "Fixes #> .git/info/exclude + ``` + + Second, mark every currently-tracked `.idea/` file with `skip-worktree` so PyCharm rewriting them does not appear in `git status` either. Run this in a shell that supports pipes (Git Bash on Windows, Terminal on macOS, or any Linux shell): + + ```shell + git ls-files .idea/ | xargs git update-index --skip-worktree + ``` + + The tracked `.idea/` files remain in the repository, but local modifications to them are ignored. Before opening a PR, confirm that `git status` does not list anything inside `.idea/`. + + If a future pull from `upstream/main` legitimately updates one of the tracked `.idea/` files, `git pull` will refuse with an error like: + + ``` + error: Your local changes to the following files would be overwritten by merge: + .idea/ + Please commit your changes or stash them before you merge. + Aborting + ``` + + If every file listed in the error is inside `.idea/`, discard your local versions (PyCharm will rewrite them the next time you open the project) and pull again: + + ```shell + git checkout HEAD -- .idea/ + git pull + ``` + + If any listed file is outside `.idea/`, stop and ask in a [discussion](https://github.com/camUrban/PteraSoftware/discussions) before running anything else. 3. **Create a new branch** - Branch from main for each change. - Use descriptive branch names, such as `feature/add_new_plot` or `bug/fix_units`. diff --git a/README.md b/README.md index d947e139..67976f58 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ *** -[![DOI](https://zenodo.org/badge/249337717.svg)](https://doi.org/10.5281/zenodo.19229119) -![license](https://img.shields.io/github/license/camUrban/PteraSoftware?color=blue) +[![DOI](https://img.shields.io/badge/DOI-10.5281/zenodo.19229119-blue)](https://doi.org/10.5281/zenodo.19229119) +![license](https://img.shields.io/badge/license-MIT-blue) ![build](https://github.com/camUrban/PteraSoftware/actions/workflows/tests.yml/badge.svg?branch=main) ![coverage](https://img.shields.io/codecov/c/gh/camUrban/PteraSoftware) ![python](https://img.shields.io/pypi/pyversions/pterasoftware) ![types](https://img.shields.io/pypi/types/pterasoftware) -![code style](https://img.shields.io/badge/code%20style-black-black) +![code style](https://img.shields.io/badge/code_style-black-black) ![source rank](https://img.shields.io/librariesio/sourcerank/pypi/PteraSoftware?color=blue&label=source%20rank) *** From cf03dc173227d0a00d469e7e6ce692f673c7414b Mon Sep 17 00:00:00 2001 From: Cameron Urban Date: Wed, 13 May 2026 16:57:56 -0400 Subject: [PATCH 02/14] Bump pre-commit pins and refresh agent docs Bring the pre-commit hooks to current upstream releases. Apply the new docformatter 1.7.8 formatting requirements (one-sentence summary lines and no blank line after closing triple-quotes) to the six modules they affect. Rewrite the agent-facing run guidance to defer to pre-commit for any hook, use bare commands from the activated venv for everything else, and drop the obsolete PYTHONPATH-based recipes. --- .pre-commit-config.yaml | 8 +-- CLAUDE.md | 20 ------- docs/RUNNING_TESTS_AND_TYPE_CHECKS.md | 75 +++++++++++++++++++++----- docs/TYPE_HINT_AND_DOCSTRING_STYLE.md | 25 +++++---- docs/WRITING_STYLE.md | 59 ++++++++++++++------ pterasoftware/_parameter_validation.py | 5 +- pterasoftware/_transformations.py | 1 - pterasoftware/convergence.py | 1 - pterasoftware/geometry/_meshing.py | 1 - pterasoftware/geometry/airfoil.py | 9 ++-- pterasoftware/geometry/airplane.py | 7 +-- 11 files changed, 135 insertions(+), 76 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27089e34..e4e94881 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,18 @@ repos: - repo: https://github.com/pycqa/isort - rev: 7.0.0 + rev: 9.0.0a3 hooks: - id: isort - repo: https://github.com/psf/black - rev: 25.11.0 + rev: 26.3.1 hooks: - id: black - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell - repo: https://github.com/PyCQA/docformatter - rev: v1.7.7 + rev: v1.7.8 hooks: - id: docformatter additional_dependencies: [tomli] diff --git a/CLAUDE.md b/CLAUDE.md index d37e81f5..b627c8d1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -204,26 +204,6 @@ Requires Python 3.11, but active development is done in 3.13 - `requirements_min.txt`: Minimum-version runtime dependencies - `setup.cfg`: Setup configuration file -## Running Scripts That Import Ptera Software - -When running scripts outside the main pterasoftware directory that import the package (e.g., scripts in `experimental/`), you need to set `PYTHONPATH` to the project root: - -```bash -cd ${WORKSPACE}/experimental && PYTHONPATH="$PWD/.." ../.venv/Scripts/python.exe script_name.py -``` - -On Unix-like systems: - -```bash -cd ${WORKSPACE}/experimental && PYTHONPATH="$PWD/.." ../.venv/bin/python script_name.py -``` - -This pattern: - -1. Changes to the script's directory -2. Sets `PYTHONPATH` to the parent directory (project root) -3. Runs the script using the virtual environment's Python interpreter - ## Common Mistakes - Forgetting to read RUNNING_TESTS_AND_TYPE_CHECKS.md before running tests and trying to use pytest (Ptera Software uses unittest) diff --git a/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md b/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md index f0cea402..ac5793bc 100644 --- a/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md +++ b/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md @@ -1,33 +1,80 @@ # Running Tests and Type Checks -Guidelines for Claude on running tests and type checks for Ptera Software. For developers, see the `CONTRIBUTING.md` file for more instructions. +> This document describes how AI coding agents (Claude Code and similar) should invoke linters, formatters, type checkers, tests, and ad-hoc scripts when working on Ptera Software. **Human contributors should follow [`CONTRIBUTING.md`](../CONTRIBUTING.md) instead**, which documents the developer-facing workflow. -## Running Tests +The guidance below assumes the project's virtual environment has already been created and activated, and that the package has been installed in editable mode via `pip install -e .` per the setup steps in `CONTRIBUTING.md`. These steps are part of normal project setup and are not repeated here. When the venv is active, `python`, `mypy`, `pre-commit`, and the other tools resolve to their venv copies on `PATH`. +## Linters, Formatters, and Spell-Checkers + +Every tool configured as a hook in `.pre-commit-config.yaml` (currently `isort`, `black`, `codespell`, `docformatter`) must always be invoked through pre-commit, never directly: + +```shell +pre-commit run --all-files # every hook, every tracked file +pre-commit run --all-files codespell # one hook against the whole tree +pre-commit run --files pterasoftware/foo.py docformatter # one hook against specific files ``` -Bash(.venv/Scripts/python.exe:-m:unittest:discover:-s:${WORKSPACE}/tests:*) -``` -If that fails, try: +Note that `pre-commit run ` without `--all-files` or `--files` only runs the hook against files currently staged in git, which is rarely what you want when verifying work in progress. + +Invoking a bare entry point such as `codespell ...` or `docformatter ...` is forbidden. The bare version can disagree with the pre-commit version because pre-commit pins each hook to a specific release in its own isolated environment, while the venv copy may have drifted ahead or behind. The pre-commit run is the source of truth that CI uses. + +## Type Checking +mypy is not a pre-commit hook today, so run it directly from the activated venv: + +```shell +mypy pterasoftware ``` -Bash(.venv/bin/python:-m:unittest:discover:-s:${WORKSPACE}/tests:*) + +mypy reads `mypy.ini` from the project root automatically. + +## Tests + +Ptera Software uses the standard library's `unittest`, not pytest. Run the full suite from the activated venv: + +```shell +python -u -m unittest discover -s tests ``` -To run a specific test module in `tests\unit\`: +For a single module: +```shell +python -u -m unittest tests.unit.test_module_name -v ``` -".venv/Scripts/python.exe" -m unittest tests.unit.test_module_name -v + +For a single test method: + +```shell +python -u -m unittest tests.unit.test_module_name.TestClass.test_method -v ``` -For example: +## Example and Experimental Scripts + +The package is editable-installed, so any script that imports `pterasoftware` works from any working directory with the bare interpreter: +```shell +python -u examples/steady_horseshoe_vortex_lattice_method_solver.py +python -u experimental/.py ``` -".venv/Scripts/python.exe" -m unittest tests.unit.test_core_wing_cross_section_movement -v + +Do not prefix with `.venv/bin/python` or `.venv/Scripts/python.exe`, do not set `PYTHONPATH`, and do not chain `cd && ...`. The activated venv puts the right interpreter on `PATH` and the editable install handles import resolution. + +## Output Discipline + +The harness captures Bash stdout through a pipe, so Python defaults to block-buffered output. Without intervention, a ten minute simulation can sit silent for the entire run and then dump everything at exit. To keep the user able to see what is happening: + +- **Always pass `python -u`** (or `python -u -m`) when invoking Python directly so stdout and stderr are line-buffered. For a tool that buffers regardless of how it is invoked, set `PYTHONUNBUFFERED=1` in the shell. +- **Never pipe** a running command's output through `tail`, `head`, `grep`, `sed`, `awk`, or any filter that batches the stream. These swallow output and emit only the trimmed remainder at exit, defeating real-time visibility. +- **Never redirect to `/dev/null`.** If output should be discarded, say so in the conversation first so the user knows nothing is being captured. +- **Short runs (under about five minutes): foreground, no redirect.** Let output stream directly into the conversation. +- **Long runs: redirect to `experimental/.log`** with the path written plainly in the Bash command, combined with `run_in_background: true` so the harness tracks completion. The `experimental/` directory is gitignored (`/experimental/` appears in `.gitignore`), so the log cannot accidentally be committed. Read excerpts from the log at meaningful checkpoints and surface them in the conversation. Never let a backgrounded job finish with no output visible to the user. + +Example pattern for a long run: + +```shell +python -u experimental/long_running_thing.py > experimental/long_running_thing.log 2>&1 ``` -## Running MyPy Type Checking +Launched with `run_in_background: true`, the file grows line by line as the script runs and can be Read at any time. -```bash -cd ${WORKSPACE} && ".venv/Scripts/python.exe" -m mypy pterasoftware -``` \ No newline at end of file +The trailing `2>&1` is required: it duplicates stderr to wherever stdout is going, so Python's logging output, NumPy/PyVista warnings, and tracebacks (all of which default to stderr) land in the same visible log instead of splitting off into the harness's separate stderr capture. Order matters: `> 2>&1` works; `2>&1 > ` leaves stderr on the terminal. diff --git a/docs/TYPE_HINT_AND_DOCSTRING_STYLE.md b/docs/TYPE_HINT_AND_DOCSTRING_STYLE.md index 019c722f..d9f44920 100644 --- a/docs/TYPE_HINT_AND_DOCSTRING_STYLE.md +++ b/docs/TYPE_HINT_AND_DOCSTRING_STYLE.md @@ -187,11 +187,10 @@ This approach: 4. **Begin descriptions with article + shape/type info for arrays** (e.g., "A (4,4) ndarray of floats...") 5. **Use present tense for descriptions** (e.g., "Returns..." not "Will return...") 6. **Avoid starting descriptions with "This..."** -7. **Never use em-dashes (—) or en-dashes (–); always use hyphens (-)** -8. **Never use multiplication sign (×); use lowercase x** -9. **Never use pi symbol (π); write "pi"** -10. **Never use approximately-equal sign (≈); use "~"** -11. **Place closing triple-quotes on their own line** +7. **Follow the ASCII Only rule in [WRITING_STYLE.md](WRITING_STYLE.md)**, which covers all character substitutions (dashes, math symbols, smart quotes, ellipsis, arrows, emojis, and other typographic Unicode) used across the project's prose, comments, and docstrings. +8. **Place closing triple-quotes on their own line** +9. **Summary line is a single sentence.** Any additional description goes in a new paragraph after a blank line. docformatter enforces this: if the first paragraph contains multiple sentences, it moves all but the first into a new paragraph. +10. **No blank line between the closing triple-quotes and the next line of code.** docformatter enforces this too: a blank gap after the docstring will be removed. ### Module-Level Docstrings @@ -721,9 +720,11 @@ def _get_mcl_points( chordwise_coordinates: np.ndarray, ) -> list[np.ndarray]: """Takes in the inner and outer Airfoils of a wing section and its normalized - chordwise coordinates. It returns a list of four column vectors containing the - normalized components of the positions of points along the mean camber line (MCL) - (in each Airfoil's axes, relative to each Airfoil's leading point). + chordwise coordinates. + + It returns a list of four column vectors containing the normalized components of + the positions of points along the mean camber line (MCL) (in each Airfoil's axes, + relative to each Airfoil's leading point). :param inner_airfoil: The wing section's inner Airfoil. :param outer_airfoil: The wing section's outer Airfoil. @@ -823,6 +824,7 @@ def add_control_surface( self, deflection: float | int, hinge_point: float | int ) -> Airfoil: """Returns a version of the Airfoil with a control surface added at a given point. + It is called during meshing. :param deflection: The control deflection in degrees. Deflection downwards is @@ -857,7 +859,9 @@ def get_resampled_mcl( self, mcl_fractions: np.ndarray | Sequence[float] ) -> np.ndarray: """Returns a ndarray of points along the mean camber line (MCL), resampled from the - mcl_A_outline attribute. It is used to discretize the MCL for meshing. + mcl_A_outline attribute. + + It is used to discretize the MCL for meshing. :param mcl_fractions: A (N,) array-like object of floats representing normalized distances along the MCL (from the leading to the trailing edge) at which to @@ -953,5 +957,4 @@ param: Type1 | Type2 - This style guide should be updated as new patterns emerge - All existing code should gradually be updated to match this style - Use `docformatter` or similar tools to help maintain consistent formatting -- Shape information is critical and must always be included in docstrings for arrays -- Avoid using hyphens or other forms of dashes in docstrings or comments. This is because they are often incorrectly wrapped by docformatter and incorrectly rendered in PyCharm's quick documentation. For example, even though not standard grammar, it's okay to write "non symmetric" instead of "non-symmetric". \ No newline at end of file +- Shape information is critical and must always be included in docstrings for arrays \ No newline at end of file diff --git a/docs/WRITING_STYLE.md b/docs/WRITING_STYLE.md index 6fcccff6..ecaeab0f 100644 --- a/docs/WRITING_STYLE.md +++ b/docs/WRITING_STYLE.md @@ -4,7 +4,7 @@ Guidelines when writing comments, docstrings, and documentation for Ptera Softwa ## Terminology -- **"Ptera Software"**: When writing as text, always write as two words without a hyphen, each being capitalized (never "ptera", "ptera software", or "PteraSoftware"). +- **"Ptera Software"**: When referring to the project, package, or codebase by its proper name in prose, always write "Ptera Software" as two capitalized words without a hyphen. Never use "ptera", "ptera software", "PteraSoftware", or "Ptera" alone in prose. This includes possessives ("Ptera Software's", not "Ptera's") and shortenings ("the Ptera Software solver", not "the Ptera solver"). Identifier strings keep their canonical form and are not affected: the GitHub repo name "PteraSoftware", the Python package "pterasoftware", and paths like "~/Documents/GitHub/PteraSoftware/" stay as-is. - **Object references**: When referring to code objects, use proper class naming convention. The capitalization indicates that we are talking about a code object, not an abstraction. You don't need to add "object" or "objects" after the class name since the capitalization already makes this clear (e.g. "update the Wings" instead of "update the Wing objects"). In summary, when talking about code objects: - GOOD: "the previous WingCrossSection" - BAD: "the previous cross section" @@ -16,30 +16,59 @@ Guidelines when writing comments, docstrings, and documentation for Ptera Softwa - **Abstract references**: When referring to abstractions, use lowercase and separate individual words with a space (e.g. "an airplane's wings are used to generate lift" and "the cross section of a wing typically has a streamlined shape known as an airfoil"). This is to distinguish them from code objects. - **CRITICAL**: Follow the formalized coordinate system naming conventions exactly as described in the `AXES_AND_COORDINATE_SYSTEMS.md` and `AXES_POINTS_AND_FRAMES.md` documents when writing about or referencing in text vector-valued variables or things such as transformation and rotation matrices. -## Running a CodeSpell Spell Check +## Running CodeSpell -```bash -cd ${WORKSPACE} && ".venv/Scripts/codespell.exe" --ignore-words=.codespell-ignore.txt --skip="*/_build/*,*.dat" +CodeSpell is configured as a pre-commit hook. Run it with: + +```shell +pre-commit run --all-files codespell ``` -## Formatting Docstrings with docformatter +## Running docformatter + +docformatter is configured as a pre-commit hook. Run it with: -```bash -cd ${WORKSPACE} && ".venv/Scripts/python.exe" docformatter --black --in-place pterasoftware -r +```shell +pre-commit run --all-files docformatter ``` +## ASCII Only + +In all written contributions to this project (code, comments, docstrings, documentation, commit messages, file contents), use only the 95 printable ASCII characters (0x20 through 0x7E: space, letters, digits, and standard punctuation). + +The constraint is on what you author. Content quoted verbatim from external sources (third-party error messages, file contents you are citing, output captured from tools, web references) does not need to be transliterated. + +### Forbidden Characters + +- Em dashes and en dashes. +- Non-ASCII arrows of any kind (rightwards-arrow, leftwards-arrow, up-arrow, down-arrow, double-arrows, etc.). +- Emojis. +- Mathematical or scientific symbol characters (Greek pi, infinity sign, plus-or-minus sign, multiplication sign, division sign, micro sign, degree sign, superscripts, subscripts, set-theory symbols, etc.). +- Smart or curly quotes and apostrophes. Use straight ASCII `"` and `'`. +- Ellipsis character. Use three periods `...`. +- Bullets, middle dots, non-breaking spaces, or any other typographic Unicode. + +### How to Handle Each Forbidden Case + +- **Em dashes are special.** Do not substitute with doubled hyphens (`--`). Instead, restructure the sentence: split into two sentences, use a comma, use a colon, or place the clause in parentheses. The doubled-hyphen substitute is banned because it tends to be overused as a verbal tic. +- **En dashes used in ranges** (for example, between two numbers or two dates) may be replaced with a plain hyphen (`-`). This is technically incorrect typography, but the en dash is forbidden, so the hyphen is the accepted substitute in range contexts only. +- **En dashes used elsewhere** (for example, as a substitute for em dash in some style guides, or to join compound modifiers) must be restructured the same way as em dashes. Do not paper over them with a plain hyphen. +- **Arrows**: write `->`, `<-`, `=>`, `<=`, or use words. +- **Math symbols**: spell them out (`pi`, `infinity` or `inf`, `+/-`, `*`, `/`, `micro`, `deg`, `^2`, etc.). +- **Quotes**: straight ASCII `"` and `'`. +- **Ellipsis**: three periods `...`. + +### When in Doubt + +If a legitimate reason to violate this rule arises (a contribution must include a file with Unicode contents, or correctness requires a specific Unicode character), flag it in the PR description rather than assume. + ## Miscellaneous Guidelines - Avoid abbreviations in text unless they are well-known in the context. -- Never hyphenate words in docstrings or comments. This is because they are often incorrectly wrapped by docformatter and incorrectly rendered in PyCharm's quick documentation. For example, even though not standard grammar, it's okay to write "non symmetric" instead of "non-symmetric". -- In documentation, docstrings, and comments, represent subtraction using hyphens surrounded by spaces ( - ); never use em-dashes (—) or en-dashes (–). -- In documentation, docstrings, and comments, never use a multiplication sign (×); always use a lowercase x or an asterisk surrounded by spaces. -- In documentation, docstrings, and comments, never use the pi symbol (π); always write "pi" instead (e.g., "2 * pi" not "2 π"). The same goes for other Greek letters (e.g., use "alpha" instead of "α"). -- In documentation, docstrings, and comments, never use the approximately-equal sign (≈); always write a tilde (~) instead (e.g., "a ~ b" not "a≈b"). +- For subtraction, use a hyphen surrounded by spaces (e.g., "a - b"). +- For multiplication, use a lowercase x or an asterisk, both surrounded by spaces (e.g., "8 x 8 panels" or "2 * pi"). +- For approximately-equal, use a tilde surrounded by spaces (e.g., "a ~ b"). - When referring to axes, coordinates, or planes, use lowercase letters without hyphens between coordinate letters and descriptors (e.g., "x axis", "y component", "xz plane", "z direction"). Never use uppercase letters for axis references in text. -- Never use emojis in code, comments, docstrings, or documentation. -- Always use straight single and double quotes, not curly ones. -- Always use "..." instead of "…". - Always end *.py file with an empty line. - Preserve existing comment structure and detail level. - Write comments as complete sentences ending with a period. diff --git a/pterasoftware/_parameter_validation.py b/pterasoftware/_parameter_validation.py index b624d846..215ed832 100644 --- a/pterasoftware/_parameter_validation.py +++ b/pterasoftware/_parameter_validation.py @@ -464,8 +464,9 @@ def nD_number_vectorLike_return_float(value: Any, name: str) -> np.ndarray: def fourByFour_number_arrayLike_return_float(value: Any, name: str) -> np.ndarray: - """Validates a value is a (4,4) array-like object. It then returns it as a (4,4) - ndarray of floats. + """Validates a value is a (4,4) array-like object. + + It then returns it as a (4,4) ndarray of floats. np.nan, np.inf, and -np.inf aren't valid values. diff --git a/pterasoftware/_transformations.py b/pterasoftware/_transformations.py index d8b25e66..cc1ac54f 100644 --- a/pterasoftware/_transformations.py +++ b/pterasoftware/_transformations.py @@ -151,7 +151,6 @@ def compute_offset_rotation_adjustment( :param offset: A (3,) ndarray representing the rotation point offset. :return: A (3,) ndarray representing the position adjustment. """ - return np.asarray((np.eye(3, dtype=float) - rotation_matrix) @ offset, dtype=float) diff --git a/pterasoftware/convergence.py b/pterasoftware/convergence.py index 7db7523d..0027a9c0 100644 --- a/pterasoftware/convergence.py +++ b/pterasoftware/convergence.py @@ -1937,7 +1937,6 @@ def _get_wing_section_num_spanwise_panels( :return: The number of spanwise Panels that results in an average Panel aspect ratio closest to the desired value. """ - this_num_spanwise_panels = start_val average_panel_aspect_ratios = [] diff --git a/pterasoftware/geometry/_meshing.py b/pterasoftware/geometry/_meshing.py index e3061d59..71ad1eda 100644 --- a/pterasoftware/geometry/_meshing.py +++ b/pterasoftware/geometry/_meshing.py @@ -366,7 +366,6 @@ def _get_mcl_points( components, and (4) the outer Airfoil's MCL points' x components. The values are normalized from 0.0 to 1.0 and are unitless. """ - # Make the MCLs for each Airfoil. First index is point number, second index is # the coordinates of that point on the MCL (in each Airfoil's axes, relative to # each Airfoil's leading point). diff --git a/pterasoftware/geometry/airfoil.py b/pterasoftware/geometry/airfoil.py index 70537980..64e013d3 100644 --- a/pterasoftware/geometry/airfoil.py +++ b/pterasoftware/geometry/airfoil.py @@ -768,10 +768,11 @@ def _populate_outline(self) -> None: @staticmethod def _validate_outline_preliminary(outline_A_lp: Any) -> np.ndarray: - """Validates the basic structure of a user's provided outline_A_lp. Only checks - for issues that cannot be fixed by normalization. Orientation dependent checks - (like x monotonicity) are deferred to _validate_outline_final() since they must - be performed after rotation correction. + """Validates the basic structure of a user's provided outline_A_lp. + + Only checks for issues that cannot be fixed by normalization. Orientation + dependent checks (like x monotonicity) are deferred to _validate_outline_final() + since they must be performed after rotation correction. :param outline_A_lp: The input to validate (can be any type initially). :return: The validated version of outline_A_lp as a (N,2) ndarray of floats. diff --git a/pterasoftware/geometry/airplane.py b/pterasoftware/geometry/airplane.py index 7cc61be7..529601f8 100644 --- a/pterasoftware/geometry/airplane.py +++ b/pterasoftware/geometry/airplane.py @@ -787,9 +787,10 @@ def validate_first_airplane_constraints(self) -> None: @staticmethod def process_wing_symmetry(wing: wing_mod.Wing) -> list[wing_mod.Wing]: - """Processes a Wing to determine what type of symmetry it has. If necessary, it - then modifies the Wing. If type 5 symmetry is detected, it also creates a second - reflected Wing. Finally, it returns a list of Wings. + """Processes a Wing to determine what type of symmetry it has. + + If necessary, it then modifies the Wing. If type 5 symmetry is detected, it also + creates a second reflected Wing. Finally, it returns a list of Wings. :param wing: The Wing to process for symmetry analysis and potential modification. From f4b6b6a06e3b268903d14f82536a0aeaf15b0486 Mon Sep 17 00:00:00 2001 From: Cameron Urban Date: Wed, 13 May 2026 18:01:08 -0400 Subject: [PATCH 03/14] Add ascii-only pre-commit hook and CI action Lock committed text to printable ASCII plus tab, LF, and CR to prevent hidden-character risks like ASCII smuggling, zero-width characters, and stray BOMs, and to keep prose typographically consistent. The hook is implemented as scripts/check_ascii_only.py and paired with a matching GitHub Actions workflow so CI catches violations on every PR. Each violation is reported with its line, column, character, codepoint, Unicode name, and UTF-8 bytes. Existing offenders are transliterated to ASCII equivalents (smart quotes to straight quotes, arrows to ->, Greek letters spelled out, math symbols replaced per the style guide); a BOM is stripped from validation/measured_wing_positions.csv. Qt Designer .ui files are excluded since Qt regenerates them on each save. --- .github/pull_request_template.md | 2 +- .github/workflows/ascii.yml | 22 +++++++ .idea/dictionaries/project.xml | 4 +- .pre-commit-config.yaml | 10 +++- CLAUDE.md | 3 + CONTRIBUTING.md | 4 +- docs/AXES_POINTS_AND_FRAMES.md | 6 +- docs/CODE_STYLE.md | 16 +++-- docs/RUNNING_TESTS_AND_TYPE_CHECKS.md | 2 +- docs/TYPE_HINT_AND_DOCSTRING_STYLE.md | 2 +- docs/WRITING_STYLE.md | 10 ++++ docs/website/_static/custom.js | 5 +- pterasoftware/_functions.py | 2 +- scripts/check_ascii_only.py | 71 +++++++++++++++++++++++ tests/unit/test_aerodynamics_functions.py | 2 +- tests/unit/test_core_airplane_movement.py | 2 +- tests/unit/test_panel.py | 2 +- tests/unit/test_transformations.py | 4 +- validation/measured_wing_positions.csv | 2 +- validation/validation_study.py | 14 ++--- 20 files changed, 152 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/ascii.yml create mode 100644 scripts/check_ascii_only.py diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a5656881..32101439 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -42,6 +42,6 @@ Identify the option that best describes the impact of your change, then delete t * [ ] If any major functionality was added or significantly changed, I have added or updated tests in the `tests` package. * [ ] Code locally passes all tests in the `tests` package. * [ ] This PR passes the ReadTheDocs build check (this runs automatically with the other workflows). -* [ ] This PR passes the `black`, `codespell`, and `isort` GitHub actions. +* [ ] This PR passes the `ascii`, `black`, `codespell`, and `isort` GitHub actions. * [ ] This PR passes the `mypy` GitHub action. * [ ] This PR passes all the `tests` GitHub actions. diff --git a/.github/workflows/ascii.yml b/.github/workflows/ascii.yml new file mode 100644 index 00000000..86ee0c6f --- /dev/null +++ b/.github/workflows/ascii.yml @@ -0,0 +1,22 @@ +name: ascii + +on: + pull_request: + push: + branches: [main] + +jobs: + ascii: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Install pre-commit + run: | + python -m pip install --upgrade pip + pip install pre-commit + - name: Run ascii-only hook + run: pre-commit run --all-files ascii-only diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index 0816a0de..8cdbf60a 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -144,7 +144,7 @@ jaffe joong joukowski - jérôme + jerome karman katz klappe @@ -282,4 +282,4 @@ zy'x - \ No newline at end of file + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e4e94881..7b587663 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,4 +17,12 @@ repos: - id: docformatter additional_dependencies: [tomli] args: [--in-place, --config, ./pyproject.toml] - files: ^pterasoftware/ \ No newline at end of file + files: ^pterasoftware/ + - repo: local + hooks: + - id: ascii-only + name: ascii-only + entry: python scripts/check_ascii_only.py + language: system + types: [text] + exclude: '\.ui$' diff --git a/CLAUDE.md b/CLAUDE.md index b627c8d1..e9c4898f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,6 +39,7 @@ Requires Python 3.11, but active development is done in 3.13 - `bug_report.md` - `feature_request.md` - `workflows/`: Directory with GitHub Actions workflows + - `ascii.yml` - `black.yml` - `codespell.yml` - `isort.yml` @@ -107,6 +108,8 @@ Requires Python 3.11, but active development is done in 3.13 - `steady_ring_vortex_lattice_method.py`: Steady ring VLM solver - `trim.py`: Trim analysis functionality - `unsteady_ring_vortex_lattice_method.py`: Unsteady ring UVLM solver +- `scripts/`: Directory with maintenance and tooling scripts + - `check_ascii_only.py`: Pre-commit hook script that flags non-ASCII characters in text files - `tests/`: Directory with unit and integration tests - `benchmarks/`: Performance benchmark scripts and saved results - `bench_parallel_biot_savart.py` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfdc3020..e599eba7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,7 +71,7 @@ Ptera Software now uses GitHub Flow to manage code contributions. If this is new Some issues and pull requests (PRs) may represent work that is actively under design or refinement. - If a PR exists, or if an issue has been assigned to or claimed by someone else, please do **not** start parallel implementation work without first checking in via a comment. A short message like "Are you still actively working on this?" or "Is this a good time to help with X?” is usually sufficient. + If a PR exists, or if an issue has been assigned to or claimed by someone else, please do **not** start parallel implementation work without first checking in via a comment. A short message like "Are you still actively working on this?" or "Is this a good time to help with X?" is usually sufficient. This helps avoid duplicated effort and ensures that contributions align with the current design direction. 2. **Set up your local environment** @@ -162,7 +162,7 @@ Ptera Software now uses GitHub Flow to manage code contributions. If this is new - Include references, derivations, or reasoning where appropriate. - Add tests that validate the behavior against known physical expectations (e.g., symmetry, limiting cases, conservation behavior). - If you’re unsure whether a change falls into this category, feel free to ask in the issue or PR thread before investing significant effort. + If you're unsure whether a change falls into this category, feel free to ask in the issue or PR thread before investing significant effort. 5. **Commit your work** - Run automated checks locally: ```shell diff --git a/docs/AXES_POINTS_AND_FRAMES.md b/docs/AXES_POINTS_AND_FRAMES.md index 6a976184..84d83242 100644 --- a/docs/AXES_POINTS_AND_FRAMES.md +++ b/docs/AXES_POINTS_AND_FRAMES.md @@ -149,9 +149,9 @@ The standard abbreviations and names are given below for reference. See the sect 1. +x: In line with (parallel, not anti-parallel, to) Airplane's velocity observed from the Earth frame 2. +y: In the direction perpendicular to first and third components\* 3. +z: In the direction perpendicular to first and second components\* -* \*There are infinite options for the second and third components that satisfy the perpendicularity requirement. Therefore, we define them using a thought experiment: Imagine three unit vectors pointing along the body axes' basis directions. There is exactly one pair of angles, which we'll call -α (note the negative sign) and β, that we can use to perform a y-z extrinsic series (or, equivalently, a z-y' intrinsic series) of rotations to construct wind axes from body axes that will exactly align the x-axis with Airplane's velocity observed from the Earth frame. This series of rotations also constructs the wind axes +y and +z basis directions. - The two angles α and β are known as the angle of attack and the angle of sideslip. Wind axes are commonly defined using these angles. This is because they are intuitively understood by many aerodynamicists: in the simplest scenarios, a positive α corresponds to the Airplane's nose pointing above its direction of travel (relative wind coming from below the aircraft), and a positive β to its nose pointing to the left of its direction of travel (relative wind coming from the right of the aircraft). However, this can seem a bit cyclical, and it obscures some subtlety in their definition: defining α and β using the convention described previously allows us to define lift as the aerodynamic force's component in the wind axes' -z basis direction, thereby making lift independent of sideslip. -* Alternative way to think about basis directions (for small |α| and |β|): +* \*There are infinite options for the second and third components that satisfy the perpendicularity requirement. Therefore, we define them using a thought experiment: Imagine three unit vectors pointing along the body axes' basis directions. There is exactly one pair of angles, which we'll call -alpha (note the negative sign) and beta, that we can use to perform a y-z extrinsic series (or, equivalently, a z-y' intrinsic series) of rotations to construct wind axes from body axes that will exactly align the x-axis with Airplane's velocity observed from the Earth frame. This series of rotations also constructs the wind axes +y and +z basis directions. + The two angles alpha and beta are known as the angle of attack and the angle of sideslip. Wind axes are commonly defined using these angles. This is because they are intuitively understood by many aerodynamicists: in the simplest scenarios, a positive alpha corresponds to the Airplane's nose pointing above its direction of travel (relative wind coming from below the aircraft), and a positive beta to its nose pointing to the left of its direction of travel (relative wind coming from the right of the aircraft). However, this can seem a bit cyclical, and it obscures some subtlety in their definition: defining alpha and beta using the convention described previously allows us to define lift as the aerodynamic force's component in the wind axes' -z basis direction, thereby making lift independent of sideslip. +* Alternative way to think about basis directions (for small |alpha| and |beta|): 1. +x: Approximately towards the front of the Airplane 2. +y: Approximately towards the right of the Airplane 3. +z: Approximately towards the bottom of the Airplane diff --git a/docs/CODE_STYLE.md b/docs/CODE_STYLE.md index 8f39dd01..b483b9d2 100644 --- a/docs/CODE_STYLE.md +++ b/docs/CODE_STYLE.md @@ -13,16 +13,20 @@ - **CRITICAL**: Follow the formalized coordinate system naming conventions exactly as described in the `AXES_AND_COORDINATE_SYSTEMS.md` and `AXES_POINTS_AND_FRAMES.md` documents when naming vector-valued variables or things such as transformation and rotation matrices. - Do not use `wcs` (or any other abbreviation) for "wing cross section" or "WingCrossSection" in variable names. Instead, always write it out in full (e.g., `wing_cross_section`, `wing_cross_section_movement`, etc.). -## Formatting with Black +## Running Black -```bash -cd ${WORKSPACE} && ".venv/Scripts/python.exe" black . +Black is configured as a pre-commit hook. Run it with: + +```shell +pre-commit run --all-files black ``` -## Running a CodeSpell Spell Check +## Running CodeSpell + +CodeSpell is configured as a pre-commit hook. Run it with: -```bash -cd ${WORKSPACE} && ".venv/Scripts/codespell.exe" --ignore-words=.codespell-ignore.txt --skip="*/_build/*,*.dat" +```shell +pre-commit run --all-files codespell ``` ## Imports diff --git a/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md b/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md index ac5793bc..7e49ae72 100644 --- a/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md +++ b/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md @@ -6,7 +6,7 @@ The guidance below assumes the project's virtual environment has already been cr ## Linters, Formatters, and Spell-Checkers -Every tool configured as a hook in `.pre-commit-config.yaml` (currently `isort`, `black`, `codespell`, `docformatter`) must always be invoked through pre-commit, never directly: +Every tool configured as a hook in `.pre-commit-config.yaml` (currently `isort`, `black`, `codespell`, `docformatter`, `ascii-only`) must always be invoked through pre-commit, never directly: ```shell pre-commit run --all-files # every hook, every tracked file diff --git a/docs/TYPE_HINT_AND_DOCSTRING_STYLE.md b/docs/TYPE_HINT_AND_DOCSTRING_STYLE.md index d9f44920..d7f460b6 100644 --- a/docs/TYPE_HINT_AND_DOCSTRING_STYLE.md +++ b/docs/TYPE_HINT_AND_DOCSTRING_STYLE.md @@ -117,7 +117,7 @@ panel = cast(_panel.Panel, object_array[i, j]) - You're certain of the type but can't prove it to the type checker - No runtime check is needed -**Avoid `cast()` for `Type | None` → `Type` narrowing** - use `assert` instead for runtime safety. +**Avoid `cast()` for `Type | None` -> `Type` narrowing** - use `assert` instead for runtime safety. ### Module Alias Pattern diff --git a/docs/WRITING_STYLE.md b/docs/WRITING_STYLE.md index ecaeab0f..4cc963c2 100644 --- a/docs/WRITING_STYLE.md +++ b/docs/WRITING_STYLE.md @@ -32,6 +32,16 @@ docformatter is configured as a pre-commit hook. Run it with: pre-commit run --all-files docformatter ``` +## Running ascii-only + +ascii-only is configured as a pre-commit hook that enforces the [ASCII Only](#ascii-only) rule below. Run it with: + +```shell +pre-commit run --all-files ascii-only +``` + +The hook reports each violation with its line, column, the offending character, its Unicode code point and name, and its UTF-8 byte sequence. The check is read-only; it never modifies files. Qt Designer `.ui` files are excluded because Qt regenerates them on every save. + ## ASCII Only In all written contributions to this project (code, comments, docstrings, documentation, commit messages, file contents), use only the 95 printable ASCII characters (0x20 through 0x7E: space, letters, digits, and standard punctuation). diff --git a/docs/website/_static/custom.js b/docs/website/_static/custom.js index c609b4c7..ba12e7d9 100644 --- a/docs/website/_static/custom.js +++ b/docs/website/_static/custom.js @@ -14,8 +14,9 @@ document.addEventListener('DOMContentLoaded', function() { // Get the headerlink anchor if it exists const headerlink = h1.querySelector('a.headerlink'); - // Split by period and rejoin with period + line break - const parts = text.replace(/\s*¶\s*$/, '').split('.'); + // Split by period and rejoin with period + line break. The escape + // matches the pilcrow (U+00B6) Sphinx appends to heading anchors. + const parts = text.replace(/\s*\u00B6\s*$/, '').split('.'); // Clear the heading h1.innerHTML = ''; diff --git a/pterasoftware/_functions.py b/pterasoftware/_functions.py index 8b901021..667b10da 100644 --- a/pterasoftware/_functions.py +++ b/pterasoftware/_functions.py @@ -522,7 +522,7 @@ def numba_1d_explicit_cross( Adapted from: https://stackoverflow.com/a/66757029/13240504 - Author: Jérôme Richard + Author: Jerome Richard Date of retrieval: 03/23/2021 diff --git a/scripts/check_ascii_only.py b/scripts/check_ascii_only.py new file mode 100644 index 00000000..414935db --- /dev/null +++ b/scripts/check_ascii_only.py @@ -0,0 +1,71 @@ +"""Verify that the given files contain only ASCII-safe characters. + +This pre-commit hook flags any character that is not tab (U+0009), line +feed (U+000A), carriage return (U+000D), or printable ASCII (U+0020 +through U+007E). It exists to enforce the project-wide convention that +committed text files stay within the 95 printable ASCII characters plus +the usual whitespace. + +Each violation is reported with its line, column, the offending +character, its Unicode code point, its Unicode name (when available), +and its UTF-8 byte sequence. +""" + +import sys +import unicodedata +from pathlib import Path + +ALLOWED: frozenset[int] = frozenset({0x09, 0x0A, 0x0D} | set(range(0x20, 0x7F))) + + +def describe(char: str) -> str: + """Format a one-line description of a disallowed character.""" + codepoint = ord(char) + try: + name = unicodedata.name(char) + except ValueError: + name = "no Unicode name" + utf8 = " ".join(f"{b:02X}" for b in char.encode("utf-8")) + return f"U+{codepoint:04X} {char!r} ({name}; UTF-8: {utf8})" + + +def find_violations(path: Path) -> list[tuple[int, int, str]]: + """Return (line, col, description) tuples for each disallowed character.""" + raw = path.read_bytes() + try: + text = raw.decode("utf-8") + except UnicodeDecodeError as exc: + return [(0, 0, f"could not decode as UTF-8 ({exc})")] + violations: list[tuple[int, int, str]] = [] + line = 1 + col = 0 + for char in text: + if char == "\n": + line += 1 + col = 0 + continue + col += 1 + if ord(char) not in ALLOWED: + violations.append((line, col, describe(char))) + return violations + + +def main(argv: list[str]) -> int: + exit_code = 0 + for arg in argv: + path = Path(arg) + try: + violations = find_violations(path) + except OSError as exc: + print(f"{path}: could not read ({exc})", file=sys.stderr) + exit_code = 1 + continue + if violations: + exit_code = 1 + for line, col, description in violations: + print(f"{path}:{line}:{col}: {description}") + return exit_code + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/tests/unit/test_aerodynamics_functions.py b/tests/unit/test_aerodynamics_functions.py index b69bd21d..d19ef2c2 100644 --- a/tests/unit/test_aerodynamics_functions.py +++ b/tests/unit/test_aerodynamics_functions.py @@ -560,7 +560,7 @@ def ref_calculate_biot_savart_velocity(S_A_a, E_A_a, P_A_a, gamma): This is a reference implementation to validate the aerodynamics functions. Formula: - v_A__I = (gamma/(4*pi)) * (r3_A / |r3_A|^2) * [r0_A · (r1Hat_A - r2Hat_A)] + v_A__I = (gamma/(4*pi)) * (r3_A / |r3_A|^2) * [dot(r0_A, r1Hat_A - r2Hat_A)] Where: r1_A = P_A_a - S_A_a diff --git a/tests/unit/test_core_airplane_movement.py b/tests/unit/test_core_airplane_movement.py index 66b224b7..879c6b89 100644 --- a/tests/unit/test_core_airplane_movement.py +++ b/tests/unit/test_core_airplane_movement.py @@ -845,7 +845,7 @@ def test_fallback_when_geometry_validation_fails(self): ) # Use parameters that would normally trigger optimization. - # Period = 0.1s, delta_time = 0.01s → steps_per_period = 10 + # Period = 0.1s, delta_time = 0.01s -> steps_per_period = 10 # num_steps = 30 > steps_per_period, so optimization would apply. num_steps = 30 delta_time = 0.01 diff --git a/tests/unit/test_panel.py b/tests/unit/test_panel.py index eaa1f541..372e15d3 100644 --- a/tests/unit/test_panel.py +++ b/tests/unit/test_panel.py @@ -181,7 +181,7 @@ def test_calculate_projected_area_45_degrees(self): normal_G = np.array([0.0, 1.0, 1.0]) # Will be normalized internally projected_area = panel.calculate_projected_area(normal_G) - # Should be area * cos(45°) = area / sqrt(2) + # Should be area * cos(45 deg) = area / sqrt(2) expected_projected_area = panel.area / np.sqrt(2) self.assertAlmostEqual(projected_area, expected_projected_area, places=10) diff --git a/tests/unit/test_transformations.py b/tests/unit/test_transformations.py index c598bf5c..e9659366 100644 --- a/tests/unit/test_transformations.py +++ b/tests/unit/test_transformations.py @@ -272,7 +272,7 @@ def test_composition_properties(self): npt.assert_allclose(v_rotated_sequential, v_rotated_composed, atol=1e-14) def test_large_angle_handling(self): - """Tests handling of large angles beyond ±180 degrees. + """Tests handling of large angles beyond +/-180 degrees. :return: None """ @@ -460,7 +460,7 @@ def test_specific_known_rotations(self): npt.assert_allclose(R_act_180, R_act_180_expected, atol=1e-14) def test_large_angle_handling(self): - """Tests handling of large angles beyond ±180 degrees. + """Tests handling of large angles beyond +/-180 degrees. :return: None """ diff --git a/validation/measured_wing_positions.csv b/validation/measured_wing_positions.csv index 10637f26..1b0bccbb 100644 --- a/validation/measured_wing_positions.csv +++ b/validation/measured_wing_positions.csv @@ -1,4 +1,4 @@ -0,2.936599985 +0,2.936599985 0.01,3.98109091 0.02,5.781667248 0.03,7.185637876 diff --git a/validation/validation_study.py b/validation/validation_study.py index e819b46c..18f45db0 100644 --- a/validation/validation_study.py +++ b/validation/validation_study.py @@ -1,6 +1,6 @@ # TODO: Redo the convergence analysis for this simulation and update the relevant # parameters if necessary. -"""This script runs a validation case of Ptera Software’s UVLM. +"""This script runs a validation case of Ptera Software's UVLM. I first emulate the geometry and kinematics of a flapping robotic test stand from "Experimental and Analytical Pressure Characterization of a Rigid Flapping Wing for @@ -13,10 +13,10 @@ More information can be found in my accompanying report: "Validating an Open-Source UVLM Solver for Analyzing Flapping Wing Flight: An Experimental Approach." """ -# Import Python’s math package. +# Import Python's math package. import math -# Import NumPy and MatPlotLib’s PyPlot package. +# Import NumPy and MatPlotLib's PyPlot package. import matplotlib.pyplot as plt import numpy as np @@ -46,7 +46,7 @@ # some small midline offset. This value is in meters. wing_midline_offset = 0.005 -# Import the extracted points from the paper’s diagram of the planform. The resulting +# Import the extracted points from the paper's diagram of the planform. The resulting # array is of the form [spanwise coordinate, chordwise coordinate], and is ordered # from the leading edge root, to the tip, to the trailing edge root. The origin is # the trailing edge root point. The positive spanwise axis extends from root to tip @@ -104,7 +104,7 @@ # Calculate the spanwise distance between the WingCrossSections. spanwise_step = (half_span - tip_inset) / num_spanwise_sections -# Define four ndarrays to hold the leading and trailing points of each section’s left +# Define four ndarrays to hold the leading and trailing points of each section's left # and right WingCrossSections (in wing axes projected onto its xy-plane, relative to # the leading edge root point). stackLeftLpsXY_Wn_Ler = np.zeros((num_spanwise_sections, 2), dtype=float) @@ -440,7 +440,7 @@ def time_normalized_validation_geometry_sweep_function_rad(time): validation_num_steps = validation_movement.num_steps validation_delta_time = validation_movement.delta_time -# Create a variable to hold the time in seconds at each of the simulation’s time steps. +# Create a variable to hold the time in seconds at each of the simulation's time steps. times = np.linspace( 0, validation_num_steps * validation_delta_time, @@ -636,7 +636,7 @@ def time_normalized_validation_geometry_sweep_function_rad(time): # z-component multiplied by negative one. exp_lifts = -1 * stackExpNetForcesZ_W -# Get this solver’s SteadyProblems' Airplanes. +# Get this solver's SteadyProblems' Airplanes. airplanes = [] for steady_problem in validation_solver.steady_problems: airplanes.append(steady_problem.airplanes[0]) From 1a984cb80fcd35a12bd60f8768b6b36cbe151ba9 Mon Sep 17 00:00:00 2001 From: Cameron Urban Date: Wed, 13 May 2026 18:07:58 -0400 Subject: [PATCH 04/14] Add docformatter CI action Catch unformatted docstrings on PRs without relying on contributors to have run pre-commit locally. The workflow installs pre-commit and runs the docformatter hook so CI uses the same pinned version and file filter as local runs. --- .github/pull_request_template.md | 2 +- .github/workflows/docformatter.yml | 22 ++++++++++++++++++++++ CLAUDE.md | 1 + 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docformatter.yml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 32101439..c55e2e93 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -42,6 +42,6 @@ Identify the option that best describes the impact of your change, then delete t * [ ] If any major functionality was added or significantly changed, I have added or updated tests in the `tests` package. * [ ] Code locally passes all tests in the `tests` package. * [ ] This PR passes the ReadTheDocs build check (this runs automatically with the other workflows). -* [ ] This PR passes the `ascii`, `black`, `codespell`, and `isort` GitHub actions. +* [ ] This PR passes the `ascii`, `black`, `codespell`, `docformatter`, and `isort` GitHub actions. * [ ] This PR passes the `mypy` GitHub action. * [ ] This PR passes all the `tests` GitHub actions. diff --git a/.github/workflows/docformatter.yml b/.github/workflows/docformatter.yml new file mode 100644 index 00000000..67beef41 --- /dev/null +++ b/.github/workflows/docformatter.yml @@ -0,0 +1,22 @@ +name: docformatter + +on: + pull_request: + push: + branches: [main] + +jobs: + docformatter: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Install pre-commit + run: | + python -m pip install --upgrade pip + pip install pre-commit + - name: Run docformatter hook + run: pre-commit run --all-files docformatter diff --git a/CLAUDE.md b/CLAUDE.md index e9c4898f..d7860d6e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,6 +42,7 @@ Requires Python 3.11, but active development is done in 3.13 - `ascii.yml` - `black.yml` - `codespell.yml` + - `docformatter.yml` - `isort.yml` - `mypy.yml` - `tests.yml` From 996cd1b3a562af8dd41be27d93cc41b38f52d3bc Mon Sep 17 00:00:00 2001 From: Cameron Urban Date: Wed, 13 May 2026 18:15:23 -0400 Subject: [PATCH 05/14] Add mypy pre-commit hook Catch type errors locally before commit instead of waiting for the mypy GitHub action to fail. mypy is fast enough on this codebase that the speed cost is negligible (sub-second with warm cache), and the hook uses language: system so it shares the venv mypy and dependency setup with interactive runs. --- .pre-commit-config.yaml | 6 ++++++ docs/CODE_STYLE.md | 8 ++++++++ docs/RUNNING_TESTS_AND_TYPE_CHECKS.md | 8 +++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7b587663..569cb0ed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,3 +26,9 @@ repos: language: system types: [text] exclude: '\.ui$' + - id: mypy + name: mypy + entry: mypy pterasoftware + language: system + always_run: true + pass_filenames: false diff --git a/docs/CODE_STYLE.md b/docs/CODE_STYLE.md index b483b9d2..4638e945 100644 --- a/docs/CODE_STYLE.md +++ b/docs/CODE_STYLE.md @@ -29,6 +29,14 @@ CodeSpell is configured as a pre-commit hook. Run it with: pre-commit run --all-files codespell ``` +## Running mypy + +mypy is configured as a pre-commit hook. Run it with: + +```shell +pre-commit run --all-files mypy +``` + ## Imports - Import Ptera Software using the following pattern: ```import pterasoftware as ps``` diff --git a/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md b/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md index 7e49ae72..6696abab 100644 --- a/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md +++ b/docs/RUNNING_TESTS_AND_TYPE_CHECKS.md @@ -6,7 +6,7 @@ The guidance below assumes the project's virtual environment has already been cr ## Linters, Formatters, and Spell-Checkers -Every tool configured as a hook in `.pre-commit-config.yaml` (currently `isort`, `black`, `codespell`, `docformatter`, `ascii-only`) must always be invoked through pre-commit, never directly: +Every tool configured as a hook in `.pre-commit-config.yaml` (currently `isort`, `black`, `codespell`, `docformatter`, `ascii-only`, `mypy`) must always be invoked through pre-commit, never directly: ```shell pre-commit run --all-files # every hook, every tracked file @@ -20,14 +20,12 @@ Invoking a bare entry point such as `codespell ...` or `docformatter ...` is for ## Type Checking -mypy is not a pre-commit hook today, so run it directly from the activated venv: +mypy is configured as a pre-commit hook and reads `mypy.ini` from the project root automatically. Run it with: ```shell -mypy pterasoftware +pre-commit run --all-files mypy ``` -mypy reads `mypy.ini` from the project root automatically. - ## Tests Ptera Software uses the standard library's `unittest`, not pytest. Run the full suite from the activated venv: From 6d5fa31d0ad8d28dba6337902fd2826cb2401c39 Mon Sep 17 00:00:00 2001 From: Cameron Urban Date: Wed, 13 May 2026 18:28:26 -0400 Subject: [PATCH 06/14] Add end-of-file-fixer pre-commit hook Normalize text file endings to a single trailing newline so future diffs are not cluttered with no-newline markers. Apply the fix in place to existing files: mostly .idea/ PyCharm config, airfoil .dat data, and a handful of docs and command files. Also add a shared workflow file for hooks sourced from the pre-commit/pre-commit-hooks repo. --- .../commands/check-unit-tests-redundancy.md | 2 +- .claude/commands/commit.md | 2 +- .../commands/debug-unit-tests-and-fixtures.md | 2 +- .../commands/make-unit-tests-and-fixtures.md | 2 +- .github/CODEOWNERS | 2 +- .github/pull_request_template.md | 2 +- .github/workflows/black.yml | 2 +- .github/workflows/pre-commit-hooks.yml | 22 +++++++++++++++++++ .idea/.name | 2 +- .idea/PteraSoftware.iml | 2 +- .idea/codeStyles/Project.xml | 2 +- .idea/codeStyles/codeStyleConfig.xml | 2 +- .idea/copilot.data.migration.agent.xml | 2 +- .idea/copilot.data.migration.ask.xml | 2 +- .idea/copilot.data.migration.ask2agent.xml | 2 +- .idea/copilot.data.migration.edit.xml | 2 +- .idea/icon.svg | 2 +- .../Ptera_Software_Default.xml | 2 +- .../inspectionProfiles/profiles_settings.xml | 2 +- .idea/jsonCatalog.xml | 2 +- .idea/markdown.xml | 2 +- .idea/misc.xml | 2 +- .idea/modules.xml | 2 +- .idea/other.xml | 2 +- .idea/scopes/to_inspect.xml | 2 +- .idea/statistic.xml | 2 +- .idea/vcs.xml | 2 +- .idea/watcherTasks.xml | 2 +- .pre-commit-config.yaml | 4 ++++ CLAUDE.md | 1 + CONTRIBUTING.md | 2 +- docs/CODE_STYLE.md | 2 +- docs/RUNNING_TESTS_AND_TYPE_CHECKS.md | 2 +- docs/TYPE_HINT_AND_DOCSTRING_STYLE.md | 2 +- docs/WRITING_STYLE.md | 2 +- docs/website/favicon/favicon.svg | 2 +- make_installer.iss | 1 - pterasoftware/geometry/_airfoils/_removed.md | 2 +- pterasoftware/geometry/_airfoils/e374.dat | 2 +- pterasoftware/geometry/_airfoils/eiffel10.dat | 2 +- .../geometry/_airfoils/fx74cl5140.dat | 2 -- pterasoftware/geometry/_airfoils/goe795sm.dat | 1 - pterasoftware/geometry/_airfoils/k3311.dat | 2 +- .../geometry/_airfoils/legionair140_sm.dat | 2 +- pterasoftware/geometry/_airfoils/psu94097.dat | 2 +- pterasoftware/geometry/_airfoils/s2027.dat | 1 - pterasoftware/geometry/_airfoils/s2046.dat | 1 - pterasoftware/geometry/_airfoils/s8021.dat | 1 - 48 files changed, 66 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/pre-commit-hooks.yml diff --git a/.claude/commands/check-unit-tests-redundancy.md b/.claude/commands/check-unit-tests-redundancy.md index 08845a51..e4b0bdf2 100644 --- a/.claude/commands/check-unit-tests-redundancy.md +++ b/.claude/commands/check-unit-tests-redundancy.md @@ -95,4 +95,4 @@ Before finalizing: - [ ] Redundancy report has been generated - [ ] No useful tests were accidentally removed - [ ] Remaining tests still provide comprehensive coverage -- [ ] `__init__.py` files are updated if needed \ No newline at end of file +- [ ] `__init__.py` files are updated if needed diff --git a/.claude/commands/commit.md b/.claude/commands/commit.md index db5d3677..3cfd83c7 100644 --- a/.claude/commands/commit.md +++ b/.claude/commands/commit.md @@ -63,4 +63,4 @@ Generate a commit message for the current staged changes, then create the commit - Never use `--no-verify` or skip pre-commit hooks. - If the pre-commit hook fails, fix the issue and create a NEW commit (do not amend). - Never push unless the user explicitly asks. -- If there are no changes to commit, inform the user and stop. \ No newline at end of file +- If there are no changes to commit, inform the user and stop. diff --git a/.claude/commands/debug-unit-tests-and-fixtures.md b/.claude/commands/debug-unit-tests-and-fixtures.md index f1aa268e..216a0ec0 100644 --- a/.claude/commands/debug-unit-tests-and-fixtures.md +++ b/.claude/commands/debug-unit-tests-and-fixtures.md @@ -131,4 +131,4 @@ Before considering debugging complete: - [ ] All potential bugs have been flagged to user - [ ] Changes are documented with reasoning - [ ] Edge cases discovered are documented -- [ ] If any modules have been updated, they've also been reformatted using black \ No newline at end of file +- [ ] If any modules have been updated, they've also been reformatted using black diff --git a/.claude/commands/make-unit-tests-and-fixtures.md b/.claude/commands/make-unit-tests-and-fixtures.md index 6a162ad9..c7410748 100644 --- a/.claude/commands/make-unit-tests-and-fixtures.md +++ b/.claude/commands/make-unit-tests-and-fixtures.md @@ -81,4 +81,4 @@ Before finalizing: - [ ] Tests are independent - [ ] Naming is consistent with project patterns - [ ] Style guidelines are followed -- [ ] `__init__.py` files are updated \ No newline at end of file +- [ ] `__init__.py` files are updated diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 78a2880d..bece14a7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,4 +10,4 @@ /.pre-commit-config.yaml @camUrban **/pyproject.toml @camUrban **/requirements*.txt @camUrban -**/setup.cfg @camUrban \ No newline at end of file +**/setup.cfg @camUrban diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c55e2e93..db8cd1d3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -42,6 +42,6 @@ Identify the option that best describes the impact of your change, then delete t * [ ] If any major functionality was added or significantly changed, I have added or updated tests in the `tests` package. * [ ] Code locally passes all tests in the `tests` package. * [ ] This PR passes the ReadTheDocs build check (this runs automatically with the other workflows). -* [ ] This PR passes the `ascii`, `black`, `codespell`, `docformatter`, and `isort` GitHub actions. +* [ ] This PR passes the `ascii`, `black`, `codespell`, `docformatter`, `isort`, and `pre-commit-hooks` GitHub actions. * [ ] This PR passes the `mypy` GitHub action. * [ ] This PR passes all the `tests` GitHub actions. diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index 7a67c210..00b09c8b 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -10,4 +10,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: psf/black@stable \ No newline at end of file + - uses: psf/black@stable diff --git a/.github/workflows/pre-commit-hooks.yml b/.github/workflows/pre-commit-hooks.yml new file mode 100644 index 00000000..22643022 --- /dev/null +++ b/.github/workflows/pre-commit-hooks.yml @@ -0,0 +1,22 @@ +name: pre-commit-hooks + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit-hooks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Install pre-commit + run: | + python -m pip install --upgrade pip + pip install pre-commit + - name: Run end-of-file-fixer hook + run: pre-commit run --all-files end-of-file-fixer diff --git a/.idea/.name b/.idea/.name index cb123527..2b0987bd 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -PteraSoftware \ No newline at end of file +PteraSoftware diff --git a/.idea/PteraSoftware.iml b/.idea/PteraSoftware.iml index 9e5f617f..053e57bc 100644 --- a/.idea/PteraSoftware.iml +++ b/.idea/PteraSoftware.iml @@ -22,4 +22,4 @@ - \ No newline at end of file + diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 853ebbd1..cfb6c2c8 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -20,4 +20,4 @@