Skip to content
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

Consider adding a mass property to nodes #96

Open
benstevens48 opened this issue Jun 14, 2017 · 18 comments
Open

Consider adding a mass property to nodes #96

benstevens48 opened this issue Jun 14, 2017 · 18 comments
Labels

Comments

@benstevens48
Copy link

Allowing nodes to have a mass property would be quite useful (so then the acceleration a produced by a force F acting on a node of mass m would be given by a = F/m, and this would also be used in calculating the centering force). For example, there might be a some nodes in your network (that may be quite important nodes for something) that you don't want to move around too much and stay quite central (but you don't want to fix completely), and giving them a large mass would achieve this. This can't be achieved by giving them a small charge since then they would no longer repel other nodes as strongly.

@curran
Copy link
Contributor

curran commented Jun 14, 2017

This great third party module implements what you describe https://github.com/vasturiano/d3-force-bounce

@benstevens48
Copy link
Author

Thanks. That seems to implement it for an alternative version of forceCollide, but I really want to use it with the other built-in forces like the many-body force, the link force and the center force. The many-body force will be the one that takes most effort to rewrite.

@curran
Copy link
Contributor

curran commented Jun 14, 2017

Perhaps @vasturiano would have some ideas on how to approach integration with other forces. I'm not familiar enough with the work to advise as to whether that's possible or how to do it.

@benstevens48
Copy link
Author

I think that all you need to do to add a mass that mimics physics is just to replace all instances of vx += RHS with vx += RHS/massOfNode and similarly for vy. The forceCenter would have to be adjusted to do a weighted average by mass instead of just an average. These are all quite minor changes. It's just a pain for me to have to create my own slightly modified copy of the code for all of them!

@curran
Copy link
Contributor

curran commented Jun 14, 2017

There's also the API considerations. Have you tried implementing it in d3-force? Any PR in the works?

@vasturiano
Copy link
Contributor

Hey there,

Just for reference it is doable to have d3.forceBounce coexist with the other (core) forces like manyBody, forceLink, etc. d3-force-bounce is just a force plugin like the others, except that it is not included in the d3-force bundle. All you have to do is import the lib and it should be available.

Here's an example of them coexisting:
https://bl.ocks.org/vasturiano/0a05e58d5122cde888793c374d587aac

@vasturiano
Copy link
Contributor

You may also want to have a look at https://github.com/vasturiano/d3-force-magnetic. This is a variant of manyBody that mimics natural attraction/repulsion forces. Possibly, the .charge(<fn>) method achieves what you are looking for.

@benstevens48
Copy link
Author

benstevens48 commented Jun 18, 2017

I had a think about the API and am not sure how best it would be implemented. The simplest thing I can think of would be to have a property m on the node like x and y etc which could be provided by the user and initialised to 1 if not provided. Then the built-in forces would be modified very slightly to set node.vx += f/node.m instead of node.vx += f (and similarly for vy). Unfortunately this would not be backwards compatible since people might be using the property m on a node for something. In addition/instead of this, there could be a function simulation.mass which accepts a function that takes a node and returns its mass (or accepts a constant mass). This could be stored as node.m. Alternatively in order to retain backwards compatibility this could be stored internally as an array but then it would have to be passed to the forces somehow which might be a bit cumbersome.

I did think about being able to set the mass separately for different forces as this would fit in better with the existing API. However I don't think this makes so much sense from a physics point of view. Here is a little digression to explain why I think respecting the physics is important. In the built-in force link force, there is a bias property which determines what proportion of the link force acts on the source and target nodes. This depends on the number of links from the node. However, having this bias rather than an equal force on each violates Newton's 3rd law (every action has an equal and opposite reaction), which it turn results in the total sum of forces on the system being potentially non-zero. This means that the center of mass of the system will accelerate according to this resultant force and can result in a drift of the nodes. It is possible to correct this using the forceCenter, but this force has its own problems. As soon as you allow a node to be fixed (e.g. by dragging) the forceCenter force has unexpected effects because the fixed node is not moved. So in my opinion it is better to have a system of internal forces which sum to zero to avoid this problem.

Although this is slightly off-topic, it would be useful (and a very easy backwards-compatible change to make) if you could set the bias for the link force in the same way as the strength property, which would allow the user to correct the problem described above.

@vasturiano
Copy link
Contributor

@benstevens48 I see what you're saying, and I agree. Ideally, we would have a framework level mass property for each node and while each plugin would calculate a force vector to apply to a node, the framework would translate that to acceleration via a = f/m. It seems that for most forces we have now we're simply doing f = a, which leads to your default of m=1. This is kind of ok for most plugins because you can manipulate the effect of m using the strength node accessors.
The two exceptions to this are forceLink and forceCenter because none of those have a strength accessor associated with nodes. forceCenter has a rather different nature (it manipulates position directly rather than velocity) so let's exclude that.
For forceLink, indeed the manner in which the force is distributed among the two nodes (which can be seen as the intent behind m in f/m) is determined by the bias which is currently hardcoded to the ratio between the number of links of each of the two nodes. Exposing bias as an accessor function, would possibly fix this conundrum as you could easily set that to be the ratio between the node masses. Would that be an acceptable solution?

And thanks for the interesting discussion!

@benstevens48
Copy link
Author

Hi @vasturiano , thanks for your reply. Allowing the user to provide a function (or constant) for the bias would be quite useful I think (independent of the mass issue). It's not quite equivalent to having a mass - you'd need to adjust the strength as well as the bias to get something equivalent. Also, for the many body force, adjusting the strength is not equivalent to having a mass. For example, setting a low strength on a node will decrease it's acceleration (like a large mass), but it will also decrease the amount it attracts/repels other nodes, which you might not want (e.g. you might want a very massive node which is also very repulsive).

So, in conclusion, I think it would definitely by worth exposing bias as an accessor function, and this would be quite easy to implement. In addition, having a framework-level mass property would be highly desirable.

@vasturiano
Copy link
Contributor

@benstevens48 setting a low strength will actually not decrease the node's acceleration, in fact it will have no effect at all in its own acceleration. This is by design and boils down from Newton's 2nd law, obvious example being two objects on earth fall with the same constant acceleration regardless of their mass.
In forceManyBody a node's strength only contributes to the attraction/repulsion effect on all the other nodes, having no effect on itself. The equation is basically strength_of_other_node/distance, which relates to the gravity acceleration: Gm/d^2 (m=mass of other node). So, being that G is a constant, one can manipulate the effect of mass using the strength node fn.

Regarding bias, I fully agree that exposing it would be not only useful but essential if one needs to specify how the link force is distributed among each of the nodes.

@benstevens48
Copy link
Author

@vasturiano , ah, I see, I thought that since the many body force could be both repulsive and attractive it was calculated according to Coulomb's law which uses the products of the two charges (strengths), but now I see that it's actually more like gravity or 'repulsive' gravity, with the strength being the mass (which cancels out because you first multiply by it to get the force then divide by it to get the acceleration, hence it doesn't appear). Having a Coulomb's law force with a mass as well would be slightly more flexible, although I will have to experiment with what works best for my scenario.

@benstevens48
Copy link
Author

Correction - I guess you can't achieve attraction between all the nodes with a Coulomb's law force so it would be a genuinely different force.

@vasturiano
Copy link
Contributor

You bring up an interesting point with Coulomb. I alluded to this on forceMagnetic (4th paragraph) but it wasn't resolved, possibly because it wasn't an issue for that use case.
Indeed neither forceManyBody or forceLink support deriving the direction of the force as a function of the charge signs in a node pair.
Almost sounds like a clustering problem (in which equal signs attract each other). 🤔 I wonder if https://github.com/ericsoco/d3-force-cluster could be of rescue here.

@vasturiano
Copy link
Contributor

vasturiano commented Jun 21, 2017

No, scrap that, equal signs repel each other. Sounds like we're talking about an entirely new force. Perhaps it could be just a new property/method option of manyBody/forceMagnetic though I'm not clear how much it will complicate the Barnes-Hut approximation (or if it's even possible).

@fefrei
Copy link

fefrei commented Dec 12, 2018

In the built-in force link force, there is a bias property which determines what proportion of the link force acts on the source and target nodes. This depends on the number of links from the node. However, having this bias rather than an equal force on each violates Newton's 3rd law (every action has an equal and opposite reaction), which it turn results in the total sum of forces on the system being potentially non-zero. This means that the center of mass of the system will accelerate according to this resultant force and can result in a drift of the nodes.

This can cause weird effects when combining with other forces. I've spent way too much time tracking down why a part of a graph that looked like this

...----A
       |
       |
       B
       |
       |
       C

was floating up. I had a custom force that pulled A and C apart to a certain distance, and links between A, B and C that are too short. The forces find an equilibrium as intended, but because of the bias, A (which has a link to the remaining graph on the left) is pulled downwards way less than C is pulled upwards by the link force, causing the whole thing to rise. This really confused me, as my force was meant to be symmetric (and it was), and I never suspected the link force to cause this weird upwards force!

This forced me to ship a customized version of the link force which simply has the bias removed.

So, in conclusion, I think it would definitely by worth exposing bias as an accessor function, and this would be quite easy to implement.

This would be perfect 👍

@Fil
Copy link
Member

Fil commented Jul 6, 2020

Related to #128

@Fil
Copy link
Member

Fil commented Jul 6, 2020

PS: It would be useful to share the problematic graph as a block or observable notebook.

@Fil Fil added the idea label Sep 1, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

5 participants