From 0b07f17f98a9c0a74cb01afb9cbd4c75cb2285dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Karakaya?= Date: Fri, 19 Jun 2026 00:59:01 +0300 Subject: [PATCH] feat(docs): add MkDocs site and GitHub Pages deploy workflow --- .github/workflows/ci-cd.yml | 2 +- .github/workflows/docs.yml | 34 +++++ .gitignore | 3 +- README.md | 5 +- docs/api_reference.md | 132 ++++++++++++++++++ docs/databases.md | 2 +- docs/development.md | 60 ++++++++ docs/examples.md | 129 +++++++++++++++++ docs/getting_started.md | 111 +++++++++++++++ docs/images/setuav_logo.png | Bin 0 -> 6423 bytes docs/index.md | 40 ++++++ docs/javascripts/mathjax.js | 11 ++ docs/motor_calibration.md | 128 ++++++++++++++--- docs/stylesheets/extra.css | 57 ++++++++ docs/usage.md | 268 +++++++++++++++++++++++++++++------- mkdocs.yml | 76 ++++++++++ pyproject.toml | 7 +- requirements.txt | 3 - 18 files changed, 989 insertions(+), 79 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/api_reference.md create mode 100644 docs/development.md create mode 100644 docs/examples.md create mode 100644 docs/getting_started.md create mode 100644 docs/images/setuav_logo.png create mode 100644 docs/index.md create mode 100644 docs/javascripts/mathjax.js create mode 100644 docs/stylesheets/extra.css create mode 100644 mkdocs.yml diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index bdc21f21..667ef597 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -27,7 +27,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e .[plot,pybamm,openmdao,dev] + pip install -e .[plot,openmdao,dev] - name: Run tests run: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..e99f039a --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,34 @@ +name: Deploy Docs + +on: + push: + branches: + - main + paths: + - 'docs/**' + - 'mkdocs.yml' + - '.github/workflows/docs.yml' + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-24.04 + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -e .[docs] + + - name: Deploy to GitHub Pages + run: | + mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore index 8390db42..b9b70778 100644 --- a/.gitignore +++ b/.gitignore @@ -10,10 +10,11 @@ venv/ # output folders *_out/ build/ +site/ .antigravitycli/ scratch/ # Test and Coverage .coverage .pytest_cache/ -htmlcov/ \ No newline at end of file +htmlcov/ diff --git a/README.md b/README.md index 9a8a5300..2a3207ca 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,6 @@ PyThrust is an open-source framework for electric propulsion system analysis, co | **3. Propeller Aerodynamic Coefficients** | **4. Hover Efficiency Heatmap** | | ![Propeller Aerodynamic Coefficients](https://raw.githubusercontent.com/Setuav/PyThrust/main/docs/images/propeller_coefficients.png) | ![Hover Efficiency Heatmap](https://raw.githubusercontent.com/Setuav/PyThrust/main/docs/images/efficiency_heatmap.png) | -### 5. PyBaMM Electrochemical Battery Simulation (Dynamic Load) -![PyBaMM Electrochemical Battery Simulation](https://raw.githubusercontent.com/Setuav/PyThrust/main/docs/images/pybamm_mission_results.png) - ## Documentation Please see the [docs/](https://github.com/Setuav/PyThrust/tree/main/docs) folder for design specifications, core mathematical model descriptions, and database details. @@ -25,4 +22,4 @@ PyThrust is licensed under the Apache License, Version 2.0 (the "License"). See ## Copyright -Copyright (c) 2026 Setuav. All rights reserved. \ No newline at end of file +Copyright (c) 2026 Setuav. All rights reserved. diff --git a/docs/api_reference.md b/docs/api_reference.md new file mode 100644 index 00000000..f8ada676 --- /dev/null +++ b/docs/api_reference.md @@ -0,0 +1,132 @@ +# API Reference + +This page summarizes the main public classes and helpers. For implementation details, see the source modules under `pythrust/`. + +## Propulsion Models + +`MotorSpec` defines brushless motor electrical parameters: + +| Field | Unit | Description | +|---|---:|---| +| `kv_rpm_per_v` | RPM/V | Motor speed constant | +| `resistance_ohm` | ohm | Winding resistance | +| `no_load_current_a` | A | Datasheet no-load current | +| `current_max_a` | A | Maximum continuous or configured current limit | +| `torque_constant_kv_ratio` | - | Optional second-order motor model ratio | +| `magnetic_lag_tau` | s | Optional magnetic lag time constant | +| `iron_loss_exponent` | - | Optional no-load current speed scaling exponent | + +Use `get_no_load_current(rpm)` and `get_winding_resistance(current_a)` when evaluating speed-dependent or current-dependent motor behavior. + +## Battery, System, and Propeller Specs + +| Class | Purpose | +|---|---| +| `BatterySpec` | Pack voltage and discharge efficiency | +| `SystemSpec` | Lumped electrical resistance for battery, ESC, wires, and connectors | +| `PropellerSpec` | Propeller geometry passed to the solver | +| `OperatingPoint` | Solved RPM, thrust, torque, power, current, voltage, efficiency, and feasibility state | + +## Propulsion Solver + +`PropulsionSolver` solves the coupled electrical and aerodynamic equilibrium for a single operating condition: + +```python +point = solver.solve_operating_point( + motor=motor, + battery=battery, + system=system, + propeller=propeller, + prop_entry=prop_entry, + rho=1.225, + airspeed_mps=15.0, + throttle=0.7, +) +``` + +`SolverConfig` controls numerical behavior: + +| Field | Default | Description | +|---|---:|---| +| `rpm_min` | `100.0` | Lower RPM bound | +| `rpm_max_margin` | `1.1` | Safety factor on estimated maximum RPM | +| `eps_rpm` | `1e-8` | RPM convergence tolerance | +| `eps_v` | `1e-8` | Voltage residual tolerance | +| `max_iter` | `100` | Maximum root-finder iterations | + +## Propeller Database + +`PropellerDatabase` loads JSON metadata and CSV performance tables: + +```python +from pathlib import Path +from pythrust.propellers import PropellerDatabase + +db = PropellerDatabase() +db.load(Path("data/propellers/apc_202602"), strict=False) +entry = db.get("APC_13x6.5E") +ct, cp = entry.get_coefficients(rpm=5000.0, advance_ratio=0.4) +``` + +Main helpers: + +| Method | Description | +|---|---| +| `load(data_dir, strict=False)` | Load every propeller JSON file in a directory | +| `load_entry(json_path, data_dir=None, strict=False)` | Load one propeller entry | +| `list_propellers()` | Return sorted propeller IDs | +| `get(prop_id)` | Return a `PropellerEntry` by ID | +| `find_by_size(diameter_in, pitch_in, blade_count=2, tolerance=0.5)` | Find the closest size match | +| `get_interpolated_coefficients(...)` | Fetch `Ct` and `Cp` through a size lookup | + +## Motor Database + +`MotorDatabase` loads brushless motor JSON files and converts catalog entries into solver specs: + +```python +from pathlib import Path +from pythrust.motors import MotorDatabase + +db = MotorDatabase() +db.load(Path("data/motors")) +motor_entry = db.get("SunnySky_X2826_KV550") +motor = motor_entry.to_spec() +``` + +Main helpers: + +| Method | Description | +|---|---| +| `load(data_dir)` | Recursively load motor JSON files | +| `load_entry(json_path)` | Load one motor JSON file | +| `list_motors()` | Return sorted motor IDs | +| `get(motor_id)` | Return a `MotorEntry` by ID | +| `search(...)` | Filter by Kv, current, and weight constraints | + +## Calibration + +`PropulsionCalibrator` fits `SystemSpec.resistance_ohm` against manufacturer or thrust-stand data: + +```python +from pythrust.propulsion import PropulsionCalibrator + +calibrator = PropulsionCalibrator(system_bounds=(0.0, 1.0)) +points = calibrator.load_csv("table.csv") +result = calibrator.calibrate( + points, + motor, + battery, + system, + propeller, + prop_entry, +) +system = result.to_system_spec() +``` + +`CalibrationResult` reports fitted resistance, thrust/current RMSE values, thrust `R^2`, convergence status, and quality warnings. + +## OpenMDAO + +`pythrust.openmdao.PropulsionComponent` wraps `PropulsionSolver` as an `ExplicitComponent` for optimization models. + +Inputs include motor parameters, battery voltage, system resistance, propeller diameter, throttle, density, and airspeed. Outputs include RPM, thrust, torque, battery current, battery power, motor current, motor voltage, and feasibility. diff --git a/docs/databases.md b/docs/databases.md index b87d86ec..6a34f97e 100644 --- a/docs/databases.md +++ b/docs/databases.md @@ -38,7 +38,7 @@ rpm,speed_mps,advance_ratio,efficiency,thrust_coeff,power_coeff,power_w,torque_n ## 2) Brushless Motor Database (`data/motors/`) -The motor database is a directory of individual JSON files representing fırçasız (brushless) motors. +The motor database is a directory of individual JSON files representing brushless motors. ### Motor Spec format (`*.json`) ```json diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 00000000..6e16133c --- /dev/null +++ b/docs/development.md @@ -0,0 +1,60 @@ +# Development & Testing + +This guide covers local development, tests, examples, and documentation publishing. + +## Local Environment + +```bash +python -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +pip install -e .[plot,openmdao,dev,docs] +``` + +## Run Tests + +```bash +pytest +``` + +The CI workflow runs the test suite on Python 3.10, 3.11, and 3.12. + +## Run Example Workflows + +```bash +PYTHONPATH=. python examples/select_motor.py +PYTHONPATH=. python examples/calibrate_from_datasheet.py +PYTHONPATH=. python examples/optimize_and_plot_propulsion.py +``` + +See [Examples](examples.md) for a user-facing walkthrough of each script. + +Generated plots are written under `docs/images/` and are used by the documentation site. + +## Documentation Site + +PyThrust uses MkDocs Material for a clean, searchable static documentation site. + +Serve locally: + +```bash +mkdocs serve +``` + +Build static HTML: + +```bash +mkdocs build --strict +``` + +Deploy manually to GitHub Pages: + +```bash +mkdocs gh-deploy --force +``` + +## GitHub Pages Publishing + +The `Deploy Docs` workflow publishes the MkDocs site when documentation files, `mkdocs.yml`, or the workflow itself change on `main`. + +In the GitHub repository settings, configure Pages to publish from the `gh-pages` branch after the first successful deploy. diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 00000000..1be010e5 --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,129 @@ +# Examples + +PyThrust includes runnable examples that show the main workflows: solving against catalog data, calibrating losses from measurements, and using OpenMDAO for propulsion co-design. + +Run examples from the repository root so relative `data/` and `docs/images/` paths resolve correctly. + +```bash +PYTHONPATH=. python examples/.py +``` + +--- + +## Requirements + +| Example | Extra dependencies | +|---|---| +| `calibrate_from_datasheet.py` | Core PyThrust dependencies | +| `select_motor.py` | `openmdao` | +| `optimize_and_plot_propulsion.py` | `openmdao`, `matplotlib` | + +Install the full example environment: + +```bash +pip install -e .[plot,openmdao] +``` + +--- + +## Datasheet Calibration + +Script: + +```bash +PYTHONPATH=. python examples/calibrate_from_datasheet.py +``` + +This example identifies the lumped system resistance for a motor, propeller, battery, ESC, and wiring setup. + +It uses: + +| Input | Value or source | +|---|---| +| Motor | Datasheet Kv, resistance, no-load current, and current limit | +| Propeller | `APC_13x6.5E` from `data/propellers/apc_202602` | +| Battery | 4S nominal voltage, `14.8 V` | +| Test table | RPM, thrust in grams, and battery current in amps | + +The output reports: + +| Metric | Meaning | +|---|---| +| System resistance | Fitted `SystemSpec.resistance_ohm` | +| Thrust RMSE | Propeller-model thrust error against measured thrust | +| Current RMSE | Battery-current prediction error | +| Thrust R2 | Fit quality for the aerodynamic thrust prediction | +| Per-point table | Predicted vs measured thrust/current for each RPM row | + +See [Motor Calibration](motor_calibration.md) for the calibration model and equations. + +![Calibration results](images/calibration_results.png) + +--- + +## Motor Selection + +Script: + +```bash +PYTHONPATH=. python examples/select_motor.py +``` + +This example combines theoretical co-design with real motor database lookup. + +Workflow: + +1. Load `APC_13x6.5E` propeller data. +2. Use OpenMDAO to find an efficient theoretical motor/propeller/throttle combination for hover. +3. Load the brushless motor database from `data/motors`. +4. Search real motors near the optimized Kv and current requirement. +5. Print the top candidates sorted by winding resistance and weight. + +The optimization target is a hover thrust of `4.903 N`, approximately `500 gf`. + +Typical output includes: + +| Output | Meaning | +|---|---| +| Target Kv | Ideal speed constant from the theoretical optimization | +| Target diameter | Optimized propeller diameter | +| Target hover current | Current at the optimized hover point | +| Minimum hover power | Battery power objective value | +| Top motor matches | Closest catalog motors with Kv, resistance, weight, and current limit | + +See [Component Databases](databases.md) for motor catalog format and query helpers. + +--- + +## Propulsion Optimization and Plotting + +Script: + +```bash +PYTHONPATH=. python examples/optimize_and_plot_propulsion.py +``` + +This example demonstrates OpenMDAO-based propulsion co-design and a parametric Kv sweep. + +It performs three stages: + +1. Run the baseline propulsion model. +2. Optimize motor Kv, propeller diameter, and throttle for a fixed hover thrust. +3. Sweep Kv and re-optimize diameter/throttle at each point. + +The generated plot is saved to: + +```text +docs/images/optimize_and_plot_results.png +``` + +The plot shows: + +| Panel | Shows | +|---|---| +| Power and propeller size vs motor Kv | Hover battery power and optimized propeller diameter | +| Throttle and RPM vs motor Kv | Optimized throttle setting and shaft speed | + +![Propulsion co-design optimization](images/optimize_and_plot_results.png) + +See [Propulsion Solver](usage.md) for the operating-point solver used inside the OpenMDAO component. diff --git a/docs/getting_started.md b/docs/getting_started.md new file mode 100644 index 00000000..2e88f4c7 --- /dev/null +++ b/docs/getting_started.md @@ -0,0 +1,111 @@ +# Getting Started + +This guide walks through installing PyThrust and solving a first propulsion operating point. + +## Requirements + +PyThrust requires Python 3.10 or newer. + +Core dependencies: + +* `numpy` +* `scipy` + +Optional extras: + +* `plot` for visualization examples +* `openmdao` for multidisciplinary design optimization workflows +* `dev` for tests +* `docs` for the MkDocs documentation site + +## Installation + +Clone the repository and install the package in editable mode: + +```bash +git clone https://github.com/Setuav/PyThrust.git +cd PyThrust +python -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +pip install -e . +``` + +For the full development environment: + +```bash +pip install -e .[plot,openmdao,dev,docs] +``` + +## First Solve + +The core workflow is: + +1. Load a propeller aerodynamic dataset. +2. Define motor, battery, system, and propeller specifications. +3. Solve the operating point for an airspeed and throttle. + +```python +from pathlib import Path + +from pythrust.propellers import PropellerDatabase +from pythrust.propulsion import ( + BatterySpec, + MotorSpec, + PropellerSpec, + PropulsionSolver, + SystemSpec, +) + +prop_db = PropellerDatabase() +prop_db.load(Path("data/propellers/apc_202602"), strict=False) +prop_entry = prop_db.get("APC_13x6.5E") + +motor = MotorSpec( + kv_rpm_per_v=860.0, + resistance_ohm=0.0258, + no_load_current_a=1.3, + current_max_a=65.0, +) +battery = BatterySpec(voltage_v=14.8) +system = SystemSpec(resistance_ohm=0.05) +propeller = PropellerSpec(diameter_m=0.3302, blade_count=2) + +solver = PropulsionSolver() +point = solver.solve_operating_point( + motor=motor, + battery=battery, + system=system, + propeller=propeller, + prop_entry=prop_entry, + rho=1.225, + airspeed_mps=15.0, + throttle=0.7, +) + +print(point.rpm) +print(point.thrust_n) +print(point.motor_current_a) +print(point.is_feasible) +``` + +## Run the Examples + +```bash +PYTHONPATH=. python examples/select_motor.py +PYTHONPATH=. python examples/calibrate_from_datasheet.py +PYTHONPATH=. python examples/optimize_and_plot_propulsion.py +``` + +The plotting and OpenMDAO examples require the optional dependencies shown above. + +See [Examples](examples.md) for the purpose, inputs, and outputs of each script. + +## Build the Documentation Locally + +```bash +pip install -e .[docs] +mkdocs serve +``` + +Then open the local MkDocs URL printed in the terminal. diff --git a/docs/images/setuav_logo.png b/docs/images/setuav_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..93908326e5694f9f67e391224de0360f21482f99 GIT binary patch literal 6423 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 Zt(yDEFS}W9f&a`Ptz62vRAB literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..0a15cf6a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,40 @@ +# PyThrust + +![PyThrust Banner](images/PyThrust_banner.png) + +Welcome to the official documentation for **PyThrust** - an open-source Python framework for electric propulsion system analysis, co-design, and parameter optimization in UAV applications. + +PyThrust combines empirical propeller data, brushless motor models, battery/system loss modeling, and OpenMDAO integration so UAV designers can move from theoretical propulsion sizing to real component choices with traceable calculations. + +--- + +## Why PyThrust? + +Electric UAV propulsion design usually crosses several domains: aerodynamics, motor electrical behavior, battery loading, component catalogs, and mission constraints. PyThrust keeps those pieces in one workflow: + +* **Coupled operating-point solver:** Solve equilibrium RPM, thrust, torque, current, power, and efficiency for a motor, propeller, battery, and flight condition. +* **Catalog-backed selection:** Query real brushless motor and propeller datasets instead of optimizing against abstract components only. +* **Calibration tools:** Fit lumped system resistance from test-stand data to account for ESC, battery, wiring, and connector losses. +* **Optimization-ready components:** Use the propulsion solver inside OpenMDAO for multidisciplinary design optimization studies. +* **Optimization-ready examples:** Generate plots for design sweeps, calibration quality, propeller coefficients, and hover efficiency maps. + +--- + +## Design and Analysis Visualization + +| Propulsion Co-Design Optimization | Propulsion Calibration & Auto-Tuning | +| :---: | :---: | +| ![Propulsion co-design optimization](images/optimize_and_plot_results.png) | ![Propulsion calibration and auto-tuning results](images/calibration_results.png) | +| **Propeller Aerodynamic Coefficients** | **Hover Efficiency Heatmap** | +| ![Propeller aerodynamic coefficients](images/propeller_coefficients.png) | ![Hover efficiency heatmap](images/efficiency_heatmap.png) | + +## Key Documentation Sections + +* [**Getting Started**](getting_started.md): Installation, optional extras, and a first operating-point solve. +* [**Propulsion Solver**](usage.md): Solver configuration, feasibility rules, and usage examples. +* [**Motor Calibration**](motor_calibration.md): Fit system resistance from manufacturer or thrust-stand data. +* [**Examples**](examples.md): Runnable scripts for calibration, motor selection, and OpenMDAO optimization. +* [**API Reference**](api_reference.md): Main classes, database loaders, calibration objects, and OpenMDAO wrapper. +* [**Mathematical Model**](theory.md): Propeller, motor, electrical loss, and coupled equilibrium equations. +* [**Component Databases**](databases.md): Propeller CSV/JSON and motor catalog formats. +* [**Development & Testing**](development.md): Local setup, tests, examples, and documentation build commands. diff --git a/docs/javascripts/mathjax.js b/docs/javascripts/mathjax.js new file mode 100644 index 00000000..d883c521 --- /dev/null +++ b/docs/javascripts/mathjax.js @@ -0,0 +1,11 @@ +window.MathJax = { + tex: { + inlineMath: [["\\(", "\\)"]], + displayMath: [["\\[", "\\]"], ["$$", "$$"]], + processEscapes: true, + }, + options: { + ignoreHtmlClass: ".*|", + processHtmlClass: "arithmatex", + }, +}; diff --git a/docs/motor_calibration.md b/docs/motor_calibration.md index 88415faf..6d66da5c 100644 --- a/docs/motor_calibration.md +++ b/docs/motor_calibration.md @@ -10,38 +10,130 @@ The `PropulsionCalibrator` identifies a single lumped parameter, `system.resista ## Physical Loss Model -### Voltage Balance +### Symbols -At a given `throttle` and battery voltage $V_{\text{bat}}$, the ideal average voltage applied by PWM switching is $V_{\text{applied}} = \text{throttle} \times V_{\text{bat}}$. +| Symbol | Meaning | +|---|---| +| $K_v$ | Motor speed constant from the datasheet | +| $R_m$ | Motor winding resistance from the datasheet | +| $I_0$ | Motor no-load current from the datasheet | +| $R_{\text{system}}$ | Lumped resistance being calibrated | +| $V_{\text{bat}}$ | Battery voltage during the test | +| $V_{\text{applied}}$ | Average voltage commanded by throttle | +| $V_{\text{back}}$ | Motor back-EMF voltage | +| $V_m$ | Motor terminal voltage | +| $I_{\text{motor}}$ | Motor winding current predicted from propeller torque | +| $I_{\text{bat,pred}}$ | Predicted battery current | +| $\tau$ | Propeller shaft torque from the aerodynamic database | -The voltage actually reaching the motor terminals is reduced by transmission voltage drops: -$$V_{\text{motor}} = V_{\text{applied}} - I_{\text{motor}} R_{\text{system}}$$ +### Step 1: Applied Voltage -Equating this to the motor's internal back-EMF voltage balance ($V_{\text{motor}} = V_{\text{back}} + I_{\text{motor}} R_m$): -$$\text{throttle} \times V_{\text{bat}} = V_{\text{back}} + I_{\text{motor}} (R_m + R_{\text{system}})$$ +At a given throttle and battery voltage, the ideal average voltage applied by PWM switching is: -where -$$V_{\text{back}} = \frac{\text{RPM}}{K_v}, \qquad I_{\text{motor}} = \frac{\tau}{K_t} + I_0, \qquad K_t = \frac{60}{2\pi K_v}$$ +$$ +V_{\text{applied}} = +\text{throttle} \cdot V_{\text{bat}} +$$ -$\tau$ is the propeller shaft torque determined from the aerodynamic database at the measured RPM. +Some of this voltage is lost before it reaches the motor because the battery, ESC, wires, and connectors all have finite resistance. -### Power Balance & Battery Current +### Step 2: Motor State at the Measured RPM -The electrical power drawn from the battery is the sum of motor power and transmission conduction losses ($I_{\text{motor}}^2 R_{\text{system}}$): -$$P_{\text{battery}} = V_{\text{motor}} I_{\text{motor}} + I_{\text{motor}}^2 R_{\text{system}} = V_{\text{back}} I_{\text{motor}} + I_{\text{motor}}^2 (R_m + R_{\text{system}})$$ +For each measured RPM, PyThrust first evaluates the propeller torque from the propeller database. That torque determines the motor current needed to spin the propeller: -Thus, the predicted battery DC current is: -$$I_{\text{bat\_pred}}(R_{\text{system}}) = \frac{V_m I_{\text{motor}} + I_{\text{motor}}^2 R_{\text{system}}}{V_{\text{bat}}}$$ +$$ +K_t = \frac{60}{2 \pi K_v} +$$ -where $V_m = V_{\text{back}} + I_{\text{motor}} R_m$ is the motor terminal voltage. +$$ +I_{\text{motor}} = +\frac{\tau}{K_t} + I_0 +$$ + +The motor back-EMF voltage is: + +$$ +V_{\text{back}} = +\frac{\text{RPM}}{K_v} +$$ + +The motor terminal voltage is then: + +$$ +V_m = +V_{\text{back}} + I_{\text{motor}} R_m +$$ + +### Step 3: Add System Losses + +The calibrated resistance is added outside the motor winding resistance: + +$$ +V_{\text{applied}} = +V_{\text{back}} ++ I_{\text{motor}} R_m ++ I_{\text{motor}} R_{\text{system}} +$$ + +Equivalently: + +$$ +V_{\text{applied}} = +V_{\text{back}} ++ I_{\text{motor}} (R_m + R_{\text{system}}) +$$ + +This is the voltage-balance view of the same physical loss model. + +### Step 4: Predict Battery Current + +The battery must supply both motor electrical power and the extra conduction loss in the system resistance: + +$$ +P_{\text{battery}} = +V_m I_{\text{motor}} ++ I_{\text{motor}}^2 R_{\text{system}} +$$ + +The predicted battery current for a candidate resistance value is: + +$$ +I_{\text{bat,pred}}(R_{\text{system}}) = +\frac{ + V_m I_{\text{motor}} + + I_{\text{motor}}^2 R_{\text{system}} +}{ + V_{\text{bat}} +} +$$ + +Here, $R_{\text{system}}$ is the only unknown parameter being fitted. --- ## Identification Procedure -Given **N** test points $\{(RPM_i, T_i, I_i)\}$ from a thrust stand, the calibrator solves the least-squares problem: - -$$\hat{R}_{\text{system}} = \arg\min_{R \in [0.0,\, 1.0]} \sum_{i=1}^N \left[ \frac{I^{\text{pred}}_i(R) - I^{\text{meas}}_i}{I_{\max}} \right]^2$$ +Given **N** test points from a thrust stand, each point contains: + +$$ +(\text{RPM}_i,\ T_i,\ I_{\text{bat,meas},i}) +$$ + +The calibrator chooses the system resistance that minimizes the normalized current error: + +$$ +\hat{R}_{\text{system}} = +\arg\min_{R \in [0.0,\, 1.0]} +\sum_{i=1}^{N} +\left( + \frac{ + I_{\text{bat,pred},i}(R) + - I_{\text{bat,meas},i} + }{ + I_{\max} + } +\right)^2 +$$ This is a linear optimization problem in $R_{\text{system}}$ and is solved using `scipy.optimize.least_squares` with bound constraints to prevent non-physical negative resistance. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 00000000..7cb9649a --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,57 @@ +/* PyThrust documentation accents, aligned with the Setuav MkDocs Material style. */ + +:root { + --pythrust-blue: #1565c0; + --pythrust-green: #2e7d32; + --pythrust-orange: #ef6c00; +} + +.md-typeset img { + border-radius: 6px; +} + +.md-typeset table:not([class]) { + font-size: 0.78rem; +} + +.md-typeset h1 { + font-weight: 700; +} + +[data-md-color-scheme="default"] .md-typeset a { + color: var(--pythrust-blue); +} + +[data-md-color-scheme="slate"] .md-typeset a { + color: #90caf9; +} + +[data-md-color-scheme="default"] .node.propStyle rect { + fill: #e3f2fd !important; + stroke: #1565c0 !important; +} + +[data-md-color-scheme="default"] .node.motorStyle rect { + fill: #f1f8e9 !important; + stroke: #558b2f !important; +} + +[data-md-color-scheme="default"] .node.batteryStyle rect { + fill: #fff3e0 !important; + stroke: #ef6c00 !important; +} + +[data-md-color-scheme="slate"] .node.propStyle rect { + fill: #1e3a8a !important; + stroke: #93c5fd !important; +} + +[data-md-color-scheme="slate"] .node.motorStyle rect { + fill: #14532d !important; + stroke: #86efac !important; +} + +[data-md-color-scheme="slate"] .node.batteryStyle rect { + fill: #7c2d12 !important; + stroke: #fdba74 !important; +} diff --git a/docs/usage.md b/docs/usage.md index f5617196..7e7383f1 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,54 +1,252 @@ # Propulsion Solver User & API Guide -This guide describes how to configure, run, and analyze the equilibrium RPM propulsion solver for single operating points and sweeps. +This guide describes how PyThrust solves a motor-propeller operating point and how to interpret the result. -## 1) Problem Statement +The solver answers one core question: -For a given throttle setting, flight airspeed, and propeller/motor specification, PyThrust solves for the equilibrium shaft speed (RPM) such that the motor's internal terminal voltage matches the applied throttle voltage: +> Given a motor, propeller, battery, throttle, and airspeed, what shaft RPM makes the electrical motor model and propeller aerodynamic load agree? +--- + +## Solver Inputs + +The operating-point solver combines five inputs: + +| Input | Class or value | Purpose | +|---|---|---| +| Motor | `MotorSpec` | Kv, winding resistance, no-load current, current limit, optional higher-order loss terms | +| Battery | `BatterySpec` | Pack voltage and discharge efficiency | +| System | `SystemSpec` | Lumped resistance for ESC, battery internal resistance, wiring, and connectors | +| Propeller | `PropellerSpec` + `PropellerEntry` | Geometry plus empirical aerodynamic coefficients | +| Flight condition | `rho`, `airspeed_mps`, `throttle` | Air density, freestream speed, and commanded voltage fraction | + +--- + +## Symbols + +| Symbol | Meaning | +|---|---| +| $\text{RPM}$ | Propeller shaft speed being solved | +| $n$ | Shaft speed in revolutions per second | +| $V$ | Flight airspeed | +| $D$ | Propeller diameter | +| $J$ | Propeller advance ratio | +| $C_t$, $C_p$ | Propeller thrust and power coefficients | +| $T$ | Thrust | +| $Q$ | Propeller shaft torque | +| $K_v$ | Motor speed constant | +| $K_t$ | Motor torque constant | +| $I$ | Motor winding current | +| $I_0$ | No-load current | +| $R_m$ | Motor winding resistance | +| $R_{\text{system}}$ | Lumped system resistance | +| $V_{\text{back}}$ | Motor back-EMF voltage | +| $V_m$ | Motor terminal voltage | +| $V_{\text{pack}}$ | Battery pack voltage | + +--- + +## What the Solver Does + +### Step 1: Evaluate the propeller at a candidate RPM + +For each candidate RPM, PyThrust converts shaft speed to revolutions per second: + +$$ +n = \frac{\text{RPM}}{60} $$ -g(\text{RPM}) = V_{\text{back}}(\text{RPM}) + I(\text{RPM}) R + I(\text{RPM}) R_{\text{system}} - \text{throttle} \times V_{\text{pack}} = 0 + +Then it calculates the propeller advance ratio: + $$ +J = \frac{V}{nD} +$$ + +The propeller database returns empirical coefficients at that RPM and advance ratio: + +$$ +C_t = C_t(\text{RPM}, J) +$$ + +$$ +C_p = C_p(\text{RPM}, J) +$$ + +From those coefficients, PyThrust computes thrust, torque, and shaft power: -Once the root of $g(\text{RPM}) = 0$ is found, the solver evaluates the complete aerodynamic and electrical state (thrust, torque, currents, powers, efficiency). +$$ +T = C_t \rho n^2 D^4 +$$ + +$$ +Q = \frac{C_p \rho n^2 D^5}{2\pi} +$$ + +$$ +P_{\text{shaft}} = C_p \rho n^3 D^5 +$$ + +### Step 2: Evaluate the motor state + +By default, PyThrust uses a first-order brushless DC motor model. The torque constant is: + +$$ +K_t = +\frac{30}{\pi K_v} +$$ + +The motor current needed to drive the propeller torque is: + +$$ +I = +\frac{Q}{K_t} + I_0 +$$ + +The back-EMF voltage is: + +$$ +V_{\text{back}} = +\frac{\text{RPM}}{K_v} +$$ + +The motor terminal voltage is: + +$$ +V_m = +V_{\text{back}} + I R_m +$$ + +### Drela / QPROP Motor Model Notes + +`MotorSpec` also includes optional parameters inspired by Mark Drela's QPROP motor models: + +| Field | Effect | +|---|---| +| `torque_constant_kv_ratio` | Adjusts the relation between speed constant and torque constant | +| `magnetic_lag_tau` | Adds magnetic lag to the back-EMF relation | +| `no_load_current_linear` | Adds a linear speed term to no-load current | +| `no_load_current_quadratic` | Adds a quadratic speed term to no-load current | +| `resistance_quadratic` | Adds current-dependent winding resistance | +| `iron_loss_exponent` | Scales no-load current with RPM using a power law | + +When these fields are left at their defaults, the model reduces to the simpler first-order motor equations above. See [Mathematical Model](theory.md) for the full equations and Drela references. + +--- + +## Equilibrium RPM + +The throttle command defines the average voltage available from the battery: + +$$ +V_{\text{applied}} = +\text{throttle} \cdot V_{\text{pack}} +$$ + +The solver searches for the RPM where the motor voltage demand plus system voltage drop equals the applied voltage: + +$$ +g(\text{RPM}) = +V_m(\text{RPM}) ++ I(\text{RPM}) R_{\text{system}} +- V_{\text{applied}} +$$ + +The equilibrium point is: + +$$ +g(\text{RPM}) = 0 +$$ + +PyThrust solves this scalar root-finding problem with Brent's method through `scipy.optimize.root_scalar`. + +--- + +## RPM Bracket + +Before solving, PyThrust builds a search interval: + +| Bound | How it is chosen | +|---|---| +| Lower RPM | At least `SolverConfig.rpm_min`; raised if airspeed and propeller `J` range require a higher RPM | +| Upper RPM | Estimated from `motor.kv_rpm_per_v * battery.voltage_v * throttle`, then expanded by `rpm_max_margin` | + +If the residual does not change sign inside the bracket, the point is returned as infeasible with reason `no_bracket`. --- -## 2) Solver Configuration (`SolverConfig`) +## Solver Configuration The numerical behavior of the root finder is controlled by `SolverConfig`: | Parameter | Type | Default | Description | -|---|---|---|---| +|---|---|---:|---| | `rpm_min` | `float` | `100.0` | Lower bound limit for RPM | -| `rpm_max_margin` | `float` | `1.1` | Safety scaling factor applied to calculated max RPM upper bound | -| `eps_rpm` | `float` | `1e-8` | Convergence tolerance for shaft speed (RPM) | -| `eps_v` | `float` | `1e-8` | Convergence tolerance for terminal voltage residuals | -| `max_iter` | `int` | `100` | Maximum iterations permitted for root finder | +| `rpm_max_margin` | `float` | `1.1` | Safety scaling factor applied to the upper RPM estimate | +| `eps_rpm` | `float` | `1e-8` | Convergence tolerance for shaft speed | +| `eps_v` | `float` | `1e-8` | Voltage residual tolerance | +| `max_iter` | `int` | `100` | Maximum root-finder iterations | --- -## 3) Example Usage +## Result Fields + +`solve_operating_point(...)` returns an `OperatingPoint`: + +| Field | Meaning | +|---|---| +| `rpm` | Solved shaft speed | +| `advance_ratio` | Propeller advance ratio at the solved point | +| `ct`, `cp` | Interpolated thrust and power coefficients | +| `thrust_n` | Thrust force | +| `torque_nm` | Propeller shaft torque | +| `shaft_power_w` | Mechanical shaft power | +| `motor_power_w` | Electrical power at motor terminals | +| `battery_power_w` | Battery-side power including system losses | +| `motor_current_a` | Motor winding current | +| `motor_voltage_v` | Motor terminal voltage | +| `propeller_efficiency` | Propulsive efficiency based on thrust power | +| `motor_efficiency` | Shaft power divided by motor electrical power | +| `system_efficiency` | Thrust power divided by battery power | +| `is_feasible` | Whether the point passed feasibility checks | +| `infeasible_reason` | Reason string when `is_feasible` is false | + +--- + +## Feasibility Rules + +An operating point is marked as infeasible when one of these checks fails: + +| Reason | Meaning | +|---|---| +| `throttle<=0` | No positive throttle command | +| `no_bracket` | The RPM search interval does not contain a valid voltage-balance root | +| `no_convergence` | The root finder did not converge | +| `current_limit` | `motor_current_a` exceeds `current_max_a` | +| `invalid_coefficients` | Propeller coefficient lookup produced non-physical values | +| `invalid_efficiency` | Computed efficiency is outside the physically expected range | + +--- + +## Example Usage Here is a complete example showing how to load a propeller dataset, define specifications, and solve for an operating point: ```python from pathlib import Path + from pythrust.propellers import PropellerDatabase from pythrust.propulsion import ( BatterySpec, MotorSpec, PropellerSpec, - SystemSpec, PropulsionSolver, + SystemSpec, ) -# 1. Load propeller aerodynamic dataset db = PropellerDatabase() db.load(Path("data/propellers/apc_202602"), strict=False) prop_entry = db.get("APC_13x6.5E") -# 2. Define component specifications motor = MotorSpec( kv_rpm_per_v=860.0, resistance_ohm=0.0258, @@ -58,13 +256,12 @@ motor = MotorSpec( battery = BatterySpec( voltage_v=14.8, - discharge_efficiency=1.0 + discharge_efficiency=1.0, ) system = SystemSpec(resistance_ohm=0.05) propeller = PropellerSpec(diameter_m=0.3302, blade_count=2) -# 3. Solve operating point solver = PropulsionSolver() point = solver.solve_operating_point( motor=motor, @@ -77,35 +274,10 @@ point = solver.solve_operating_point( throttle=0.7, ) -# 4. View results -print(f"RPM : {point.rpm:.1f}") -print(f"Thrust : {point.thrust_n:.2f} N") -print(f"Battery Current : {point.battery_power_w / battery.voltage_v:.2f} A") -print(f"Is Feasible : {point.is_feasible}") -``` - ---- - -## 4) Feasibility Rules - -An operating point is marked as infeasible (`point.is_feasible = False`) if: -- `motor_current_a > current_max_a` (motor current limit exceeded) -- `ct < 0` or `cp < 0` or `advance_ratio < 0` (aerodynamic coefficients out of physical range) -- No valid RPM bracket with a voltage sign change is found. - ---- - -## 5) PyBaMM Electrochemical Battery Simulation Example - -You can run a dynamic flight mission simulation that integrates the **Single Particle Model (SPM)** lithium-ion battery solver from **PyBaMM** with the propulsion model. The simulation will calculate dynamic cell voltage, state of charge (SoC) via electrochemical diffusion, and system thrust. - -To run the PyBaMM example: -```bash -PYTHONPATH=. .venv/bin/python examples/simulate_pybamm_mission.py +print(f"RPM : {point.rpm:.1f}") +print(f"Thrust : {point.thrust_n:.2f} N") +print(f"Motor Current : {point.motor_current_a:.2f} A") +print(f"Battery Power : {point.battery_power_w:.1f} W") +print(f"Feasible : {point.is_feasible}") +print(f"Reason : {point.infeasible_reason}") ``` -This generates a detailed plot showing: -- Throttle profile and terminal voltage under dynamic loads (capturing electrochemical voltage sag and relaxation recovery). -- Non-linear State of Charge (SoC %) based on discharge capacity. -- Motor current draw and produced thrust. - -The plot is saved to [pybamm_mission_results.png](file:///home/huseyin/setuav/PyThrust/docs/images/pybamm_mission_results.png). diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..da791798 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,76 @@ +site_name: PyThrust +site_description: Electric propulsion system co-design, analysis, and optimization framework for UAVs +site_author: Hüseyin Karakaya +repo_url: https://github.com/Setuav/PyThrust +repo_name: Setuav/PyThrust +edit_uri: edit/main/docs/ + +theme: + name: material + logo: images/setuav_logo.png + favicon: images/setuav_logo.png + font: + text: Ubuntu + code: Ubuntu Mono + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: white + accent: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: black + accent: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.tabs + - navigation.sections + - navigation.top + - search.suggest + - search.highlight + - content.code.copy + +extra_css: + - stylesheets/extra.css + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.tabbed: + alternate_style: true + - pymdownx.arithmatex: + generic: true + +extra_javascript: + - javascripts/mathjax.js + - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js + +nav: + - User Guide: + - Home: index.md + - Getting Started: getting_started.md + - Propulsion Solver: usage.md + - Motor Calibration: motor_calibration.md + - Examples: examples.md + - Reference: + - API Reference: api_reference.md + - Mathematical Model: theory.md + - Component Databases: databases.md + - Developer Guide: + - Development & Testing: development.md diff --git a/pyproject.toml b/pyproject.toml index 95a70c75..8a70f1c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,15 +30,16 @@ dependencies = [ plot = [ "matplotlib>=3.4", ] -pybamm = [ - "pybamm>=25.0", -] openmdao = [ "openmdao>=3.20.0", ] dev = [ "pytest>=7.0", ] +docs = [ + "mkdocs-material>=9.5", + "pymdown-extensions>=10.0", +] [tool.setuptools.dynamic] version = {attr = "pythrust.__version__"} diff --git a/requirements.txt b/requirements.txt index c8f75835..8d6589d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,9 +5,6 @@ scipy>=1.7 # Optional Dependencies (For visualization and plotting) matplotlib>=3.4 -# Optional Dependencies (For electrochemical battery simulation examples) -pybamm>=25.0 - # Optional Dependencies (For Multidisciplinary Design Optimization - MDO examples) openmdao>=3.20.0