Skip to content

Commit

Permalink
Update Intro Documentation (#28)
Browse files Browse the repository at this point in the history
Fixes #27 

- [x] Update the demos files to better elaborate on what's going on
- [x] Add new demos files
- [x] Link to demos from the source documentation
- [x] Link to feature tests as additional sources of examples beyond the
demos

To preview updates, you can check out the branch
`documentation-intro-material` and then open up the latest HTML in the
directory `docs/generated/html/`. For example, `firefox
docs/generated/html/getting-started.html`
  • Loading branch information
buckbaskin committed Feb 25, 2024
1 parent 5ce60af commit f7b5267
Show file tree
Hide file tree
Showing 35 changed files with 483 additions and 240 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/demo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ jobs:
uses: actions/checkout@v3
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- run: bazel run //demo/...
- run: for f in $(bazel query //demo/...); do bazel run ${f} || break ; done
- run: echo "🍏 This job's status is ${{ job.status }}."
15 changes: 12 additions & 3 deletions demo/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@ load("@rules_python//python:defs.bzl", "py_binary")
load("@pip_deps//:requirements.bzl", "requirement")

py_binary(
name = "orbital-model",
srcs = ["src/orbital_model.py"],
main = "src/orbital_model.py",
name = "symbolic-model",
srcs = ["src/symbolic_model.py"],
main = "src/symbolic_model.py",
deps = [
"//py:formak",
],
)

py_binary(
name = "ekf",
srcs = ["src/ekf.py"],
main = "src/ekf.py",
deps = [
"//py:formak",
],
Expand Down
100 changes: 100 additions & 0 deletions demo/src/ekf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""
# EKF
This file demonstrates defining a symbolic model with some simple approximate
physics. Then, compile it into a Python EKF implementation
The same symbolic model could also be compiled into a C++ implementation.
See featuretests/python_library_for_model_evaluation/simple_to_ekf_test.py and
the featuretests/ directory for additional features and examples.
"""

from formak.ui import Model, symbols, Symbol
from formak import python

from collections import defaultdict

dt = symbols("dt") # change in time


def main():
"""
The main function encapsulates a few common steps for all models:
1. Defining symbols
2. Identifying the symbol(s) used in the model state
3. Identifying the symbol(s) used in the control inputs
4. Defining the discrete state update
5. Constructing the Model class
The file also demonstrates:
6. Compiling from the symbolic class to a Python model implementation
7. Running a model update with the Python model
"""

# 1. Defining symbols
vp = vehicle_properties = {k: Symbol(k) for k in ["m", "x", "v", "a"]}
fuel_burn_rate = Symbol("fuel_burn_rate")

# 2. Identifying the symbol(s) used in the model state
state = set(vehicle_properties.values())

# 3. Identifying the symbol(s) used in the control inputs
control = {fuel_burn_rate} # kg/sec

# 4. Defining the discrete state update

# momentum = mv
# dmomentum / dt = F = d(mv)/dt
# F = m dv/dt + dm/dt v
# a = dv / dt = (F - dm/dt * v) / m

F = 9.81

state_model = {
vp["m"]: vp["m"] - fuel_burn_rate * dt,
vp["x"]: vp["x"] + (vp["v"] * dt) + (1 / 2 * vp["a"] * dt * dt),
vp["v"]: vp["v"] + (vp["a"] * dt),
vp["a"]: (F - (fuel_burn_rate * vp["v"])) / vp["m"],
}

# 5. Constructing the Model class
symbolic_model = Model(dt, state, control, state_model, debug_print=True)

initial_state = {
vp["m"]: 10.0,
vp["x"]: 0.0,
vp["v"]: 0.0,
vp["a"]: 0.0,
}

# 6. Compiling from the symbolic class to a Python EKF implementation
python_ekf = python.compile_ekf(
symbolic_model=symbolic_model,
process_noise={fuel_burn_rate: 1.0},
sensor_models={"simple": {vp["v"]: vp["v"]}},
sensor_noises={"simple": {vp["v"]: 1.0}},
)

# 7. Running a process model update with the Python EKF
state_vector = python_ekf.State.from_dict(initial_state)
control_vector = python_ekf.Control.from_dict({fuel_burn_rate: 0.01})
state_variance = python_ekf.Covariance()

print("Initial State")
print(state_vector)

state_vector_next, state_variance_next = python_ekf.process_model(
0.1, state_vector, state_variance, control_vector
)

print("Next State")
print(state_vector_next)

return 0


if __name__ == "__main__":
import sys

sys.exit(main())
103 changes: 0 additions & 103 deletions demo/src/orbital_model.py

This file was deleted.

162 changes: 162 additions & 0 deletions demo/src/symbolic_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"""
# Symbolic Model
This file demonstrates defining a symbolic model with some simple approximate
physics. To use this model, it is compiled into a Python implementation.
The same symbolic model could also be compiled into a C++ implementation.
See featuretests/python_library_for_model_evaluation/simple_test.py and the
featuretests/ directory for additional features and examples.
"""

from formak.ui import Model, symbols, Symbol
from formak import python

from collections import defaultdict

dt = symbols("dt") # change in time

G = gravitational_constant = 6.674e-11 # m^3 / kg / s**2
Earth_Mass = 5.9722e24 # kg
Earth_Equatorial_Radius = 6378.137 # m


def SaturnVMass():
"""
Summarize the mass distribution from the Apollo 7 launch vehicle. This mass
will be used to initialize the state of the model.
The details of the exact masses don't impact the definition of the model
"""
# Apollo 7
masses = {
"SIB_dry": 84530,
"SIB_oxidizer": 631300,
"SIB_fuel": 276900,
"SIB_other": 1182,
"SIBSIVBinterstage_dry": 5543,
"SIBSIVBinterstage_propellant": 1061,
"SIVB_dry": 21852,
"SIVB_oxidizer": 193330,
"SIVB_fuel": 39909,
"SIVB_other": 1432,
"Instrument_dry": 4263,
"Spacecraft_dry": 45312,
}

dry = 0
consumable = 0

for mass_description, mass_kg in masses.items():
if "dry" in mass_description:
dry += mass_kg
else:
consumable += mass_kg

assert (dry + consumable) == sum(masses.values())

mass_groups = defaultdict(float)
for mass_description, mass_kg in masses.items():
group = mass_description.split("_")[0]
mass_groups[group] += mass_kg

print("Mass Groups")
for k, v in sorted(list(mass_groups.items())):
print(" {}: {}".format(k, v))

return {"dry": dry, "consumable": consumable}


Vehicle_Mass_Properties = SaturnVMass()


# states, calibrates, constants


def gravitational_force(m_1, m_2, r):
"""
Calculate the gravitational force acting between two bodies.
This function can be called with floating point values or symbolic values
representing the possible masses and radii between the two masses. For this
example, it's used to encapsulate the calculation of the gravitational
force acting on the simplified model of a launch vehicle.
"""
return -G * (m_1 * m_2) / (r**2)


def main():
"""
The main function encapsulates a few common steps for all models:
1. Defining symbols
2. Identifying the symbol(s) used in the model state
3. Identifying the symbol(s) used in the control inputs
4. Defining the discrete state update
5. Constructing the Model class
The file also demonstrates:
6. Compiling from the symbolic class to a Python model implementation
7. Running a model update with the Python model
"""

# 1. Defining symbols
vp = vehicle_properties = {k: Symbol(k) for k in ["m", "x", "v", "a"]}
fuel_burn_rate = Symbol("fuel_burn_rate")

# 2. Identifying the symbol(s) used in the model state
state = set(vehicle_properties.values())

# 3. Identifying the symbol(s) used in the control inputs
control = {fuel_burn_rate} # kg/sec

# 4. Defining the discrete state update

# momentum = mv
# dmomentum / dt = F = d(mv)/dt
# F = m dv/dt + dm/dt v
# a = dv / dt = (F - dm/dt * v) / m

F = gravitational_force(vp["m"], Earth_Mass, vp["x"] + Earth_Equatorial_Radius)

state_model = {
vp["m"]: vp["m"] - fuel_burn_rate * dt,
vp["x"]: vp["x"] + (vp["v"] * dt) + (1 / 2 * vp["a"] * dt * dt),
vp["v"]: vp["v"] + (vp["a"] * dt),
vp["a"]: (F - (fuel_burn_rate * vp["v"])) / vp["m"],
}

# 5. Constructing the Model class
symbolic_model = Model(dt, state, control, state_model, debug_print=True)

initial_state = {
vp["m"]: Vehicle_Mass_Properties["dry"] + Vehicle_Mass_Properties["consumable"],
vp["x"]: 0.0,
vp["v"]: 0.0,
vp["a"]: 0.0,
}

# 6. Compiling from the symbolic class to a Python model implementation
python_model = python.compile(symbolic_model=symbolic_model)

# 7. Running a model update with the Python model
state_vector = python_model.State.from_dict(initial_state)
control_vector = python_model.Control.from_dict({fuel_burn_rate: 0.0})

print("Initial State")
print(state_vector)

state_vector_next = python_model.model(
dt=0.1, state=state_vector, control=control_vector
)

print("Next State")
print(state_vector_next)

return 0


if __name__ == "__main__":
import sys

sys.exit(main())

0 comments on commit f7b5267

Please sign in to comment.