bagherilab / ARCADE

ARCADE: Agent-based model of heterogeneous cell populations in dynamic microenvironments
Other
9 stars 1 forks source link

Add functionality to divide voxels across a specified plane #17

Open Jannetty opened 22 hours ago

Jannetty commented 22 hours ago

Hello all, I am seeking a review on the following proposed design for adding functionality that allows for voxel division along user-specified planes.

Proposed Plan:

  1. In src/env/location, define a Plane class that stores
    • a public final 3D point $(x_0, y_0, z_0)$ (an ArrayList<Integer> of length 3)
    • a public final 3D normal vector $(a,b,c)$ (an ArrayList<Integer> of length 3).
  2. In src/env/PottsLocation.java define a splitVoxels function that takes a plane object as a parameter (instead of a direction) and splits voxels on either side of the plane.
    • This will require a helper function to get the distance between a voxel and the plane using the equation:
    • $d = a(x-x_0) + b(y-y_0) + c(z-z_0)$ where
    • $(x_0, y_0, z_0)$ is the point in the plane class
    • $(a,b,c)$ is the normal vector in the plane class
    • $(x,y,z)$ is the voxel
    • if $d > 0$, the voxel is on one side of the plane, if $d < 0$ it is on the other side of the plane, if $d=0$ it is on the plane and is divided randomly to one side or the other.
  3. In src/env/PottsLocation.java, add an overloaded split function that takes a plane object instead of a direction as a parameter. This function will call the above-implemented splitVoxels using this plane as a parameter. Planes of division can now be instantiated in agents or in agent modules and passed to this split function.

Additional options:

  1. I could also modify the direction enums to contain information for making plane object for each specified plane. I could then instantiate a plane when a direction is specified to split and only ever use the plane parameter splitVoxels. This change would let there be only one splitVoxels function and would allow all split logic to be contained in one split function rather than having two implementations (one that works with a direction and one that works with a plane). However it could decrease efficiency when dividing along one of the specified planes.
  2. I could forego making a Plane class altogether and, instead of taking Plane objects as parameters, rely on the user to pass a plane's point and normal vector as parameters. I think it makes sense to encapsulate these descriptors in a class, but I also recognize that the plane itself is not an object in the simulation so that may be confusing.

Specific feedback I would like:

  1. Should I make a Plane class?
  2. Should I treat the existing direction planes the same way I will treat my new planes? I.e. consolidate the split logic and splitVoxel functions as described in Additional Option 1? (I can still do that consolidation without a Plane class).
  3. Any other thoughts you may have!

Sorry I know this is really nitty gritty but I'm hoping to get some design feedback before I implement. Thank you all!

jessicasyu commented 5 hours ago

I think a Plane class definitely makes sense -- I would pop it into core.util instead, which makes it more clear that it's not a physical thing in the simulation. You can have the Plane class contain the distance calculation.

A single splitVoxels function that takes a Plane instead of Direction + the existing Direction enum returning the corresponding Plane all sounds good as well. I wouldn't worry about the efficiency at this stage, if you want to make sure, you could do some benchmarking after it is implemented.

Two questions:

  1. How will you handle the current getDirection functionality? Is the idea that there now two main options: (a) split given only the RNG does the current default behavior of getting the direction based on shorted axis and (b) split given all parameters lets you specify any possible split
  2. How will the offsets parameter be used? Is it captured in the Plane center point or is it used separately?
Jannetty commented 2 hours ago

Thanks Jessica!

  1. Yes I was thinking given just the RNG the split function will call the same getDirection method. When getDirection returns a direction, that direction will be used to make a plane (using parameters stored in the Direction enum) and will be passed to the split function with all the parameters (more detailed description of these split functions is below).
  2. I was initially thinking of having the offset as separate so that there exists an option to provide a direction (via the enum) and an offset, but I think it would be more straightforward to make the point in the plane the offset. A proliferation module could use the getOffset function to get the point when making the plane, and when a plane is not specified the split function can make a plane using getCenter and enum parameters. So there would exist three split functions:
    • split(RNG) : makes plane using getCenter and getDirection, passes to split(RNG, Plane, Probability)
    • split(RNG, Direction) : makes a plane using getCenter and specified Direction, passes to split(RNG, Plane, Probability)
    • split(RNG, Plane, Probability) : Does splitting

Does this seem reasonable?

jessicasyu commented 1 minute ago

Yeah, that all sounds great!