-
Notifications
You must be signed in to change notification settings - Fork 138
add Force Based Motion Planning (FMP) #456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ivedynamics and cleaned up plotting functions
|
Hm, the docs in https://juliadynamics.github.io/Agents.jl/previews/PR456 do not appear, @Libbum do you know why? How do the docs build for #447 but not for this one...? |
|
There's a missing entry in the CI tasks. Usually when docs build there's an entry for |
Codecov Report
@@ Coverage Diff @@
## master #456 +/- ##
==========================================
- Coverage 92.97% 87.74% -5.24%
==========================================
Files 17 17
Lines 1224 1297 +73
==========================================
Hits 1138 1138
- Misses 86 159 +73
Continue to review full report at Codecov.
|
|
I think it was that @rmcsqrd the example file has a huge amount of code duplication. Can you please eliminate code duplication as much as possible by defining a reusable functions with keywords? For example: for i in 1:model.num_agents
## compute position around circle
theta_i = (2*π/model.num_agents)*i
xi = r*cos(theta_i)+x/2
yi = r*sin(theta_i)+y/2
xitau = r*cos(theta_i+π)+x/2
yitau = r*sin(theta_i+π)+y/2
## set agent params
pos = (xi, yi)
vel = (0,0)
tau = (xitau, yitau) ## goal is on opposite side of circle
radius = model.FMP_params.d/2
agent_color = agent_init_color(i, model.num_agents) ## This is new
add_agent!(pos, model, vel, tau, agent_color, :A, radius, model.space.extent, [], [])
add_agent!(tau, model, vel, tau, agent_color, :T, radius, model.space.extent, [], [])
endthis shuold be made a function like Until the docs build online, can you please build them locally and make sure that they are nice? You can uncomment every other example in In the meantime, I will look at the "true source" of this PR, which are the changes in the |
Because this PR is not coming from the JuliaDynamics repo, instead from rmcsqrd's fork. So we can't build them unfortunately. |
| FMP_Parameters | ||
| The parameters for the FMP model as defined in the FMP paper. Helper function `FMP_Parameter_Init` | ||
| is used to initialize the struct with default parameters. Users can modify FMP default parameters | ||
| through the usage of keyword arguments. See [the original paper](https://arxiv.org/abs/1909.05415) | ||
| for a complete description of model parameters. Parameters included in this struct that are not | ||
| present in the original paper are: | ||
| - `obstacle_list`: list of obstacles in the state space | ||
| - `interaction_array`: n x n boolean array (n = number of agents) where `interaction_array[i,j]=1` | ||
| indicates that the ith and jth agents are interacting. Used so that `interacting_pairs` is only called | ||
| once per iteration rather than every time `update_vel` is called. | ||
| - `agents`: an iterator of the interacting agents from `interacting_pairs` at each time step. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This documentation string needs improvement. Why don't you state all parameters that this struct takes in? I don't think it is useful to refer the user to some paper, when we can simply state this information here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is fair - I will update it to describe the parameters in the doc string. I will note that some of the parameters (c1, c2) seem to be empirically defined hyperparameters. The paper literally just refers to them as "two positive constant values".
In general my thought was to provide fmp_parameter_init() to remove any user guessing about how to parameterize the FMP algorithm by providing the default values used in the original paper. I want to expose the underlying parameters in case users decide they want to modify them for some reason but wanted to make the algorithm as plug and play as possible. My approach is admittedly a little clunky so if you have a better idea of how to approach this I am interested in hearing it.
|
@rmcsqrd in the example file you still say
but that is no longer true, right? |
examples/FMP.jl
Outdated
| mutable struct FMP_Agent <: AbstractAgent | ||
| id::Int | ||
| pos::NTuple{2, Float64} | ||
| vel::NTuple{2, Float64} | ||
| tau::NTuple{2, Float64} | ||
| color::String | ||
| type::Symbol | ||
| radius::Float64 | ||
| SSdims::NTuple{2, Float64} ## include this for plotting | ||
| Ni::Array{Int64} ## array of tuples with agent ids and agent positions | ||
| Gi::Vector{Int64} ## array of neighboring goal IDs | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of these parameters, how many are mandatory for the FMP algorithm? If there are more than just pos, vel, we could extend the @agent macro here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will use the @agent macro in the example. Realistically we need id, pos, vel, tau, type, and Ni.
color is really just for plotting to make the visualizations look nice. radius is right now used just for plotting and in my example I pull it from model.FMP_params.d. This is probably not a good idea because model.FMP_params.d is what is actually used for the FMP force computations. SSdims is really just used for plotting. Gi is a list of goal objects near an agent that I added in for my thesis that isn't arguably part of the FMP implementation but I do think it is useful so that users don't need to poll interacting_pairs themselves. If you want me to remove that utility I am open to it.
I think the big question I have is how to handle plotting? Without modification, the default agent size when plotted is pretty small which is why I have a plot function like as_f(a) = 1200*1/minimum(a.SSdims)*a.radius. As I understand it, when plotting a simulation, the ac, as, am functions only pass the agent as an argument. My preference would be to call model.space.extent in lieu of agent.SSdims but I am not sure how to make that possible. If you have an idea on how to access model within ac,as,am then we could remove SSdims and potentially radius and possibly color.
examples/FMP.jl
Outdated
|
|
||
| # Next, we define the model. The FMP algorithm has several parameters defined | ||
| # in the original paper which are stored in a struct. Typical values can be | ||
| # generated using the `FMP_Parameter_Init` function and loaded into the model |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FMP_Parameter_Init is mentioned both here and in the source code but doesn't exist. It has to be replaced with fmp_parameter_init.
| """ | ||
| fmp_parameter_init() | ||
| A convenience function for initializing the FMP_Parameters struct with typical FMP | ||
| parameters. Users can modify the FMP algorithm parameters through the usage of | ||
| keyword arguments. | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It truly doesn't make sense to have this function without stating what keywords it accepts. @rmcsqrd you might be confused with how documentation strings work in Julia: only what you, the developer, writes in the docstring is what a user sees. A user cannot see the keywords a function takes in unless you explicitly tell them what they are.
|
The functions |
src/spaces/continuous.jl
Outdated
| to handle interagent collisions. This function is called each time `agent_step!` is called. This | ||
| function is used in conjunction with the `update_vel!` keyword argument when a `ContinuousSpace` | ||
| is initialized. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way this is stated is missleading. This function is not called every time agent_step! is called. Rather, the user must manually call it every time they use agent_step!, for the algorithm to work.
|
@rmcsqrd this needs a bit more polish before we continue. One of the things that happens at the moment is that there are a lot of manual demands: the user must do a lot of specific steps and have a specific form of But before trying to improve on that, I'll wait for you to return here and do the easy fixes I have already outlined so far. |
|
@Datseris Thanks for all the feedback. I'm getting busy with writing my thesis so I need to put this PR on the backburner for a little - just FYI. I'll work on addressing the comments when I have a chance. |
|
No problem, take your time! |
|
@rmcsqrd I will be finishing this PR over the course of the next 2 days if you can't make it. EDIT: Ah, this is unfortunate. I cannot finish this PR myself because of the comment:
I would prefer not having to read the paper to explain all parameters as I'm also short on time. Thus, when you have the time, please provide an explanation of the parameters. |
this is in to address JuliaDynamics#458. Name of the submodule pending...
|
@Datseris Sure I will work on addressing all your comments today. |
|
@Datseris I picked up your comments at https://github.com/rmcsqrd/Agents.jl/tree/FMP_2. I was getting an error when I tried to push to my |
|
Try |
|
@rmcsqrd I'll have a look locally! |
|
@rmcsqrd Is this what the first example is supposed to look like: output1.mp4 |
|
Yes, example 1 is as depicted in your attached video. Example 2 is same as example 1 but with colors. Example 3 has a line of agents with a moving obstacle. I'll also need to remove the |
|
I already did it in last commit! Wait I'll push now! |
|
Sweet, thanks for fixing that and verifying the docs. What are next steps on my end? I think I have addressed all of your comments so far. One thing that I will note is that at some point I think I broke the FMP implementation as noted in #412 (comment) - agents don't come into a gridlock condition when they do a "circle swap" as depicted in |
I will have a look locally on how we can streamline the interface from a user's perspective. Something you can do is trying to come up with a meanigful test that we can put in our unit tests. |
|
@rmcsqrd I'm trying to understand.... why do the targets and obstacles need to be For me, this functionality you contribute here is quite similar to the Pathfinding functionality that was implemented in PR #447 . There we realized that most things related with pathfinding should not be agents but part of the model or space. Do you think something similar can be done here, or indeed every goal and every obstacle needs to be an agent? I see that when you create the agents, the fields (Side remark: EDIT: Also, I think we should rename all FMP stuff to FBMP, as the algorithm is not Forced Motion Planning (which is what I thought aaaaall this time) but Force Based Motion Planning :D EDIT 2: out of the fields of |
|
@rmcsqrd some side comment: |
Noted, just Python habits coming over. I will modify.
"Because that's the way I wrote it" 😁
All this makes sense. When you're talking about
What is better? I know it is going to be a list of
FMP is what the authors refer to the algorithm as in the original paper - I'll defer to you on what you want to call it but my preference is to stick with the authors.
mutable struct FMP_Agent <: AbstractAgent Whether or not they live in the Agent struct or somewhere else is up for debate but those are all required in some way. |
|
Just to clarify the algorithm is styled as "Force-based Motion Planning" - I mislabeled it in the PR title and probably everywhere else. |
|
@rmcsqrd We don't have to port everything to be part of the space, but we should try to eliminate all the manual tuning from the user that has to explicitly make things like the model stepping function being exactly like: function model_step!(model)
fmp_update_interacting_pairs!(model)
for agent_id in keys(model.agents)
fmp_update_vel!(model.agents[agent_id], model)
end
endWhy don't you wrap this above to a single function It is not necessary to put everything to be property of the space. although it could lead to a cleaner set up in the end. it is a bit tricky to see at the moment. Let's start streamlining this piece by piece, and the picture of what is the exported api will become clearer. then we can see if we can put things somewhere specific.
Yes, that is a valid point. And the "types" are also valid. To streamline this, you can provide a custom agent sub-type, e.g. and then the users can inherit from it using the The examples are very convoluted regarding the agent fields and coloring. Perhaps we can simplify this as well? The agents do not need to have a "color" field. A function can decide the color based on the agent ID and or agent "type". Because we need to get out a stable version soon due to a manuscript we have submitted, this might not make it into v4.2. But I promise you it will make it into v4.3! |
|
@Datseris Thanks for the comments - all of that sounds like a good direction.
That totally works for me. I am graduating in May so things are getting pretty busy for me right now. |
Codecov Report
@@ Coverage Diff @@
## master #456 +/- ##
==========================================
- Coverage 92.97% 87.74% -5.24%
==========================================
Files 17 17
Lines 1224 1297 +73
==========================================
Hits 1138 1138
- Misses 86 159 +73
Continue to review full report at Codecov.
|
…ly want one call to interacting pairs, may revise this
|
Hi! After a quick glance over the source code (assuming it's functional in its current state) and the thread above, here are my thoughts:
It would really be great to see this brought to completion, it's a really cool feature! |
|
Hi @pmbaumgartner, Finishing this has been in the back of my mind for a while - it fell off after my thesis and during the summer. Thanks for the motivation to pick this back up. Thanks for the input @AayushSabharwal, I will use that as guidance in conjunction with the other outstanding items brought up previously in this PR. All your comments are well taken; I have some follow-up thoughts on a few of them:
How about in
There is a little bit of discussion on this previously in the PR - if you look at the actual algorithm there isn't much of a difference between "agents" and "obstacles/targets". When calculating the attractive/repulsive forces for a specific agent, the agent is agnostic to whether or not it is being repulsed by another agent or an obstacle. That said if there is way to handle obstacles/targets that is more conformant with other pathfinding modules I would appreciate if you could elaborate more on that. This PR is attempting to implement the underlying algorithm as purely as possible but I'm happy to adapt to fit any
This is a good point - I'll clean up the agent struct. There are some algorithm hyperparameters that I'd like input on whether or not to expose. For example, the Φ parameter controls the repulsive force between agents. The original FMP algorithm uses a value which is reflected here - theoretically a user may have an interest in modifying this value but that is probably not a common use case. I tend to err on the side of exposing everything (while also providing the default value) but understand this can be cumbersome. Do you have thoughts on this? |
|
@rmcsqrd Glad you're still thinking about it and willing to move things forward. Based on the list @AayushSabharwal created, are there places you feel comfortable delegating some of that work to me? Or does it make more sense for you to complete this wholesale? For context I'm pretty comfortable with the Agents.jl API and where things should go to be consistent, and less comfortable with all the awesome math going on behind the scenes with an algorithm like this.
My thought here is that targets should work the same way that they do with the regular pathfinder (i.e. an in-bounds point passed to |
Sounds great!
The existing pathfinding only supports static obstacles, so they're "baked" into a grid, which tells whether that grid cell can be walked on or not. I imagine this doesn't work for FMP, since you need to calculate forces from each obstacle. If the obstacles are static in FMP, maybe store them in the FMP struct? If the obstacles or targets move too, then having a flag in the agent struct is the way to go.
If this works with the algorithm, it's probably a good idea. Having a consistent API simplifies things significantly, and minimizes the names we have to export.
If hyperparameters are agent-specific, they're probably better off in the agent struct. If they're global, they should be in the parameters struct. However, any agent-specific data that the algorithm needs but the user shouldn't modify (like agent paths in A* pathfinding) should be in the parameters struct too. It feels like the Another idea that came to my mind: Instead of iteratively calculating the force between every pair of agents/obstacles/targets manually, could this be done using ModelingToolkit.jl/DiffEq.jl using the integrator interface? I'm curious as to whether this might simplify the implementation and/or improve performance, since it would only require creating the requisite equations. That said, it also raises questions of how to handle adding/removing agents in the middle of the simulation. It might be possible using the callbacks API. That said, it's just an idea. If this ends up raising more problems than solutions, it's probably better to finish the implementation as it stands, and explore this later. If you, @Datseris or @Libbum have any thoughts on this I'd love to hear them. |
|
Hey guys! Love to see the progress on this one. Sorry that I've been inactive here! Unfortunately I am currently on vacations for a month or so, and unlikely I will provide useful feedback here during this time! But @AayushSabharwal I trust you to guide things around and help with progressing this! When time is ripe for a "final review and merge" I can try to come back! |
Sure, I'll do my best. Another thing to look into: Julia supports unicode variable names, so variables such as |
Continuation of #412.
I addressed the comments made in #412 (julia syntax formatting, more concise functions for
ac,as,am, and moved plotting toInteractiveDynamics).As a note, I implemented as a new function rather than as a form of
update_velfor reasons mentioned here: #412 (comment)