Skip to content

Conversation

@kjjarvis
Copy link
Contributor

@kjjarvis kjjarvis commented Jan 13, 2023

This model describes the dynamics of a small UAV and generates predictions of a flyable trajectory given a set of waypoints.

This work was previously developed by @matteocorbetta and @portia19, and model development is described in Corbetta et al. 2019, "Real-time UAV trajectory prediction for safely monitoring in low-altitude airspace".

PR includes all necessary functionality for:

  • Vehicle model (including controller, multiple vehicle types, etc.)
  • Trajectory generator (NURBS algorithm)

Future work described here: nasa/progpy#49

Included: changes to match Matteo's newest code, changes to what is required from user input and warnings/exceptions if wrong input given,  checking if speeds required for input ETAs are beyond limits, revert to dx implementation with vehicle state calculation, getting rid of integrator_fn parameter, added PD controller
Added computations for event_state and threshold_met, also deleted previous integrator parameter (already built in to ProgPy)
Added capability for user to input data of different units, and checks for appropriate data. Cleaned up assert/exceptions.
@github-actions
Copy link

Thank you for opening this PR. Each PR into dev requires a code review. For the code review, look at the following:

  • Reviewer should look for bugs, efficiency, readability, testing, and coverage in examples (if relevant).
  • Ensure that each PR adding a new feature should include a test verifying that feature.
  • All tests must be passing.
  • All errors from static analysis must be resolved.
  • Review the test coverage reports (if there is a change) - will be added as comment on PR if there is a change
  • Review the software benchmarking results (if there is a change) - will be added as comment on PR
  • Any added dependencies are included in requirements.txt, setup.py, and dev_guide.rst (this document)
  • All warnings from static analysis must be reviewed and resolved - if deemed appropriate.

@kjjarvis kjjarvis requested a review from portia19 January 13, 2023 22:27
Copy link
Collaborator

@teubert teubert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Submitting partial review. I'm still working on it.

Great work! It looks really great and I'm so excited that you have it working. I made a few suggestions. Let me know if you have any questions. Otherwise, I'll continue reviewing tomorrow.

Have a great long weekend!

from warnings import warn
from prog_models.exceptions import ProgModelInputException

class UAVGen(PrognosticsModel):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is UAVGen the clearest name for this? What about just UAV or UAVFlight (since it's a model of its flight)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question and we've been talking about it.
Probably UAVFlight would be the best in this situation. However, we've also talked about increasing the ``modularity'' of this model, but generating a controller that then is assigned to the UAV model. Also, trajectory generation is independent of the UAV model (up to minor things like: what type of UAV it is (rotorcraft vs. fixed wind vs. something else, maximum speed/acceleration possible, and stuff like that).
So maybe we should re-factor this to make it indeed modular? don't know if we want to leave it for the future.

Copy link
Collaborator

@teubert teubert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more comments. I'll look more this afternoon

wypt_z.append(z_temp)
self.parameters['waypoints'] = {'waypoints_time': wypt_time_unix, 'waypoints_x': wypt_x, 'waypoints_y': wypt_y, 'waypoints_z': wypt_z, 'next_waypoint': 0}

# Generate trajectory
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is a trajectory different from waypoints? Additional detail? Could use a comment here for people not familiar

def event_state(self, x : dict) -> dict:
# Extract next waypoint information
num_wypts = len(self.parameters['waypoints']['waypoints_time']) - 1 # Don't include initial waypoint
index_next = self.parameters['waypoints']['next_waypoint']
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The model itself should be stateless. Some algorithms might require that you run multiple states through the model (e.g., when building a surrogate model). This will break down if you save state in the parameters. In this case, the next_waypoint is part of the state of the system.

next_waypoint should be part of the state vector and updated in next_state, or it should be calculated each time.

Since this is a bigger change, I'm ok with leaving it as is (with a note about this limitation) if you add this to the github issue for the next release.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@teubert I'd love to talk with you more about this. For now, I've added it to the future work issue but hopefully we can get it done before the PR is finalized

}
else:
# Trajectory has passed acceptable bounds of final ETA - simulation terminated
warn("Trajectory simulation extends beyond the final ETA. Either the final waypoint was not reached in the alotted time (and the simulation was terminated), or simulation was run for longer than the trajectory length.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the final waypoint hasn't been met yet, but we're running behind? Is that a case you would want to support

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will continue simulating for a buffer time defined by the parameter "final_time_buffer_sec" past the final ETA time. Originally I implemented it this way because I was thinking that if the final waypoint wasn't met, the simulation would go on forever. But now I'm realizing that the default horizon would eventually stop this. However, once we pass the final ETA, we won't have any new input information, so the results quickly level out to whatever output corresponds to that input. Does that make sense? I'm happy to leave it as is or get rid of this and let it run to horizon

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK- I see why you implemented it that way it is. If you're simulating to threshold wouldn't it stop anyways at the last waypoint, so it wouldn't simulate past the last input information.

I guess that doesn't help for simulate to. I'm hoping you can remove this when we move to the modular, since input would be provided by the future loading equation instead. I don't like the idea of having two horizons

@matteocorbetta
Copy link
Contributor

I noticed that, generally in prognostic_model, the method simulate_to_threshold initializes u before initializing x, such that when future_loading_eqn is called the first time x=None.
for this specific case, initializing x first would be beneficial, as we would skip the if statement in future_loading_eqn all together. Would be possible to initialize x before u in simulate_to_threshold? what's the con about it?

I'm having a deja-vu (I believe we already talked about it but not sure what the problem would be).

kjjarvis and others added 4 commits January 17, 2023 17:37
Editing imports for readability, other readability changes, adjusting outputs, adding __init__
…e lat-lon-alt in one shot instead of for loop. output container generated with a for loop instead of explicitly calling all keys. However, this might need to be changed in the future when only a subset of the state will become an output. future_loading_new compressed. there's still the issue of having vehicle_model.state AND x as both states.
if params['R'] is None: params['R'] = np.diag([500, 4000, 4000, 4000]) # T, Mu, Mv, Mw
if params['qi'] is None: params['qi'] = np.array([100, 100, 1000]) # Integral error weights in position x, y, z
if params['i_lag'] is None: params['i_lag'] = 100 # integral lag: how far back to use (in data points, so 1 point = 1 dt) for integral error

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here I cleaned up some controller parameter's definition which have been there for a while (my fault). The trajectories now look a little cleaner as well.

coord_end = geom.Coord(route_.lat[0], route_.lon[0], route_.alt[0])
self.coord_transform = deepcopy(coord_end)
wypt_time_unix = [datetime.datetime.timestamp(route_.eta[iter]) - datetime.datetime.timestamp(route_.eta[0]) for iter in range(len(route_.eta))]
wypt_x, wypt_y, wypt_z = coord_end.geodetic2enu(route_.lat, route_.lon, route_.alt) # computing way-points in ENU in one shot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here there was a for loop I substituted (functions in geom.Coord work with vectors).

self.vehicle_model.set_state(state=state_temp + dxdt*self.parameters['dt'])

# I'd suggest a more compact way of generating the StateContainer
return self.StateContainer(np.array([np.atleast_1d(item) for item in dxdt]))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in-line for loop to generated state container. it's just more compact.

.. [0] M. Corbetta et al., "Real-time UAV trajectory prediction for safely monitoring in low-altitude airspace," AIAA Aviation 2019 Forum, 2019. https://arc.aiaa.org/doi/pdf/10.2514/6.2019-3514
"""
events = ['TrajectoryComplete']
inputs = ['T','mx','my','mz']
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inputs = ['T','mx','my','mz']
inputs = []

Since 'T','mx','my','mz' are calculated by the controller, which is internal to the model, I think these should not be listed as inputs. Do I understand that correctly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is an issue with our current implementation, where the "inputs" are defined in the internal "future_loading" function. They still act as inputs in the normal sense of u in the overall ProgPy framework, even though they aren't user defined. If I change the code as suggested here, I get an error, since u is still used throughout (for example, in the dx calculation on line 309). The more we review, the more I think it's time to switch to a more modular version ...

Copy link
Collaborator

@teubert teubert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few mostly cosmetic suggestions. I want to run a few more tests tomorrow, then I think I'll be done with the review


import geometry as geom
from .nurbs import generate_3dnurbs, generate_intermediate_points, evaluate, knot_vector
import prog_models.models.uav_model.trajectory.load_trajectories as load
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import prog_models.models.uav_model.trajectory.load_trajectories as load
import .load_trajectories as load

kjjarvis and others added 2 commits January 19, 2023 08:43
Imports clean up, deleting unnecessary pass, clean up example
@github-actions
Copy link

Benchmarking Results [Update]
From:

Test Time (s)
import main 0.5203941
import thrown object 0.3694851
model initialization 0.0002543000000000406
set noise 3.599999999992498e-06
simulate 0.0004221999999999282
simulate with saving 0.0010303000000000395
simulate with saving, dt 0.0023735000000000284
simulate with printing results, dt 0.002874000000000043
Plot results 0.016665400000000052
Metrics 0.0004058000000000117
Surrogate Model Generation 1.7256279
surrogate sim 0.0012356999999996177
surrogate sim, dt 0.0028638999999999193

To:

Test Time (s)
import main 0.7339252
import thrown object 0.42582069999999994
model initialization 0.0002721999999999447
set noise 3.700000000161907e-06
simulate 0.0004230000000000622
simulate with saving 0.0010056999999998872
simulate with saving, dt 0.002341099999999985
simulate with printing results, dt 0.0029359999999998276
Plot results 0.017172500000000035
Metrics 0.00036989999999992307
Surrogate Model Generation 1.8115577000000003
surrogate sim 0.0012560999999999822
surrogate sim, dt 0.002867800000000198

@teubert teubert merged commit a830a1b into dev Jun 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants