Wow! So you're interested in contributing - thank you so much!
First, read the AeroSandbox roadmap in AeroSandbox/aerosandbox/README.md
.
After reading that, here's what else you need to know before contributing:
If you're developing, install AeroSandbox in editable mode. In other words:
- First:
- For most developers (i.e. those that haven't yet been officially added as collaborators to the ASB repository):
- Fork the repository on GitHub.
- Clone your forked repository from GitHub to your computer.
- For developers that have been officially added as collaborators to the ASB repository on GitHub:
- Clone the AeroSandbox repository from GitHub to your computer.
- For most developers (i.e. those that haven't yet been officially added as collaborators to the ASB repository):
- Then, do these steps:
- On your computer, open up a terminal in the AeroSandbox root directory. (To check that you're in the right place, view your current directory with either
dir
orls
depending on your OS; you should see a file calledsetup.py
.) - If you already have an AeroSandbox installation on your computer, first uninstall that (
pip uninstall aerosandbox
). - Install the cloned copy of your repository in editable mode (
-e
), and with all optional dependencies (pip install -e .[full,test,docs]
). - Switch to the develop branch for normal use (
git checkout develop
) - While on the develop branch, create a new branch if you want to make changes (
git checkout -b feature/insert-your-feature-name-here
)
- On your computer, open up a terminal in the AeroSandbox root directory. (To check that you're in the right place, view your current directory with either
- From here, you can make your changes. After you are finished:
- Make sure that your branch passes tests by running pytest from terminal (
pytest
). - Verify that your code (at least loosely) follows the coding standards in this document.
- Make sure your work is committed and pushed.
- Create a pull request on GitHub targeting the
develop
branch of the main ASB repository.
- Make sure that your branch passes tests by running pytest from terminal (
We use the Git branching model posted here (this is by far the most common model Git branching model, so if you've used Git before on a collaborative project, you probably already know this). Main points:
- Never commit directly to
master
! - Add features in new branches named
feature/insert-name-here
. Be sure to branch these features off ofdevelop
, not off ofmaster
! - When your feature is ready in your feature branch:
- First, merge
develop
into your feature branch to get latest changes and check that all unit tests still pass (runpytest
in terminal in the project root directory). - Submit a pull request to merge it back into
develop
and wait for a core developer to approve.
- First, merge
Some other guidelines:
-
Commit often. In general, commit after every "unit" of work or anytime the code base returns to a "working" (i.e. tests passing) state. If you find yourself working for more than an hour or writing >100 lines since your last commit, consider whether a "unit" of work has been done. (Perhaps it has, perhaps it hasn't - just think about it.)
-
Write concise but descriptive commit messages - think "Google search" type language. Examples:
- "add method is_red to class House in house.py" (preferred)
- "add is_red to House" (acceptable)
- "add methods to house.py" (acceptable)
- "blah" (not acceptable)
One exception to the rule: if you're just adding/changing comments or documentation with no code changes, you can just write "docs" (or similar) as your commit message and be done.
-
Some AeroSandbox-specific notes:
-
Use
aerosandbox.numpy
functions everywhere where possible. If this throws an error during testing or if you do not find the function you need here, notify a core developer. -
You should never need to import CasADi or try to manually determine array type anywhere outside of
aerosandbox/numpy/
. If for some reason you need to do CasADi-specific things, do them within a function insideaerosandbox/numpy
- this keeps the split between engineering code and numerics code clean. -
AeroSandbox code is extensively documented; if you're ever not sure how to use something, call
help()
on it in console! E.g.help(asb.Airplane)
.
-
-
All new classes should extend one of the classes in the top-level file
common.py
. In particular, all explicit analyses (e.g. workbook-style aero buildups) should extendExplicitAnalysis
and all implicit analyses (i.e. analyses that involve iteratively solving nonlinear systems of equations) should extendImplicitAnalysis
. All other classes should extendAeroSandboxObject
. Also, all user-facing classes should contain a__repr__
method. -
Write unit tests for all new functionality using
pytest
. Make sure the tests pass before submitting a pull request.
This is all pretty standard across all scientific computing in Python:
-
As far as code style goes, we use standard Python PEP8 naming conventions:
variable_names_use_snake_case
def function_names_also_use_snake_case():
class ClassNamesUsePascalCase:
-
Use long, descriptive variable names. Use
temperature
instead ofT
. Usewing_tip_coordinate_x
instead ofwtx
. In the age of modern IDEs with autocomplete and one-click refactoring, there is no reason not to obfuscate meaning with short variable names. Long variable names also force you to split complicated expressions onto multiple lines; this is a good thing (see the following point).-
Spread expressions across multiple lines based on natural groupings of ideas. Generally, all functions with multiple input parameters should have each parameter on a new line unless it exceptionally short (something like
range(3,10)
, for example). Some examples of discouraged and encouraged coding standards:### This is discouraged: distance = ((x_start - x_end) ** 2 + (y_start - y_end) ** 2) ** 0.5 ### Instead, do this: distance = ( (x_start - x_end) ** 2 + (y_start - y_end) ** 2 ) ** 0.5
### This is discouraged: plt.plot(time, temperature, ".-", color='lightgrey', alpha=0.6, label="Temperature over time") ### Instead, do this: plt.plot( time, temperature, ".-", color='lightgrey', alpha=0.6, label="Temperature over time" )
-
-
All engineering quantities (i.e. quantities with units) used anywhere in AeroSandbox are expressed in base metric units, or derived units thereof (meters, newtons, meters per second, kilograms, etc.).
- This is true even for quantities that are traditionally expressed in non-base-metric units. Some common "gotchas":
battery_capacity
is in units of joules (not watt-hours)temperature
is in Kelvin (not Celsius)elastic_modulus
is in units of pascals (not GPa)altitude
is in meters (not feet)radio_frequency
is in Hz (not MHz)
The only exception is when units are explicitly noted as a suffix in a variable name: for example
battery_capacity_watt_hours
is in units of watt-hours, andaltitude_ft
is in units of feet. - This is true even for quantities that are traditionally expressed in non-base-metric units. Some common "gotchas":
-
Every new user-facing function is required to be documented by a docstring of some sort, with no exceptions. Google-style docstrings are preferred but not required - as long as your docstring is intelligible to an average engineer who might come across it, you're fine. It is highly recommended (but not required) that you also do the following:
- Document the purpose, function, and expected input type(s) of every input parameter within this docstring.
- Type hint all functions that you write.
We illustrate both of these requirements with the following example:
from typing import List, Tuple, Union # Note: List, Tuple, Dict, Union, etc. (note capitalization) need to be imported from the built-in "typing" def is_my_dog_happy( temperature: float, # <-- this is a type hint for a parameter! n_boats: int, # note that Python doesn't enforce the type you specify, they're just "hints" for the user coffee_brands: Union[List[str], Tuple[str]] # You can denote multiple acceptable inputs with "Union", imported from "typing" ) -> bool: # <-- this is a type hint for a return! """ This function tells me if my dog is happy today. Example: >>> if is_my_dog_happy(300, 10, ["Illy", "Peet's"]): >>> celebrate() Args: temperature: The temperature outside, in Kelvin [float] n_boats: The number of boats I see on the Charles River [int] coffee_brands: An iterable of the names of various brands of coffee [List[str]] Returns: Whether or not my dog is happy [bool] """ return True # Of course my dog is always happy!
- Also notice that in this example above, we put each parameter on its own line. Generally, do this.
- Include usage examples in runnable Python in for each function in its docstring (demarcated by
>>>
)
-
With rare exceptions, do not type the same sequence of characters more than twice. For example:
### This is discouraged: print(sol(x)) print(sol(y)) print(sol(z)) ### Instead, do this: for var in [x, y, z]: print(sol(var))
-
Use keyword arguments for functions, unless those functions are exceptionally simple, well-known, or self-explanatory. For example:
import aerosandbox as asb ### This is discouraged: atmosphere = asb.Atmosphere(7000) ### Instead, do this: atmosphere = asb.Atmosphere(altitude=7000)
-
When defining a new function: never, never make the default value of a parameter a mutable data type. Common culprits are empty lists
[]
and empty dicts{}
. Debugging this common mistake is often very difficult and wastes other devs' time. For an explanation of why this is such bad practice, read this article: "Python Mutable Defaults are the Source of All Evil".Any IDE worth its salt will likely yell at you if you do this - listen to it!
- As an example of what NOT to do:
def bad_function( my_parameter=[], # Don't do this another_param={}, # Or this!!! ): pass ### Instead, do this: def good_function( my_parameter: List=None, another_param: Dict=None, ): ### Set defaults if my_parameter is None: my_parameter = [] if another_param is None: another_param = {}
There is just one rule when interacting with this project:
- Be respectful to everyone.
Breaking this up:
- "respectful":
- Use kind and welcoming language.
- Respect different viewpoints and the experiences that lead to them.
- Gracefully accept constructive criticism.
- Do not waste others' time by trolling or arguing in bad faith.
- Do not insult, harass, or dox others.
- Stay respectful even when others may be disrespectful.
- "everyone": Everyone means everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
This Code of Conduct is loosely adapted from the Contributor Covenant.