HPInc / HP-Digital-Microfluidics

HP Digital Microfluidics Software Platform and Libraries
MIT License
3 stars 1 forks source link

Add paths to DML #282

Open EvanKirshenbaum opened 10 months ago

EvanKirshenbaum commented 10 months ago

Looking at the new OSU macros (#281), it's becoming clear that a lot of the complexity of these macros would be reduced if DML had a notion of path. Essentially, this is simply Macro[(DROP), DROP], but I'd rather not get into doing a general functional type syntax yet, so I'm willing to handle this as a special case.

Basically, what I want to do is to be able to say

path p1 = up 2 : right : down 4;
d : p1;

or maybe (as a synonym)

d : follow p1;
d : walk p1;

There's already a notion of MotionValue, which is, I think, essentially what we need internally. A MotionValue is already a CallableValue([Drop], Drop]), but we'll want a conversion to go the other way as well.

Much of the components of a path are already there in the injection logic, where things like up 2 : right already parse as Drop to Drop macros. And the logic is already there to put things like prompt, print, and pause in the middle. Some things that aren't there yet:

I'm not actually sure whether it makes sense for a path to begin with something that takes no arguments, but since I tried it, I should probably allow it. I should probably be strict about it being NO_VALUE to NO_VALUE, DROP to DROP, and (perhaps?) DROP to NO_VALUE (for things like printing based on the drop). NO_VALUE to DROP looks as though it's likely to be error-prone, as it implies that some drop is being identified.

If I'm going to do this, it probably makes sense to also allow

p = path 1 + path 2;

as a synonym for

p = path 1 : path 2;

although I may have to look into how these two interact. Currently, addition binds tighter than injection, so a : b + c : d is equivalent to a : (b + c) : d, whereas to my eye it should be (a : b) + (c : d). I could obviate the problem by making it

a : b followed by c : d

instead, of course. I'll have to think about it.

One other thing that is almost certainly needed is the notion of repetition. I'm currently thinking of something like

p repeated 5 times
p repeated for 3 seconds
p repeated until n == 0

I was going to say that repeated should bind looser than injection, but in

d : up 2 : down 3 repeated  5 times

it really doesn't make any sense for the first injection to be in the repetition. So it pretty much has to be read as

d : up 2 : (down 3 repeated 5 times)

and if you want the other reading, you can say

d : (up 2 : down 3) repeated 5 times
Migrated from internal repository. Originally created by @EvanKirshenbaum on Jun 30, 2023 at 4:04 PM PDT.
EvanKirshenbaum commented 10 months ago

This issue was referenced by the following commits before migration:

EvanKirshenbaum commented 10 months ago

The messy part about putting paths in protocols is that much of what you want to do with them is mixing and splitting drops, and it really seems as though these operations should be made first class parts of the language.

I'm seeing three simple operations and a few complex ones that can be put off to the future. The complicated ones are

The straightforward (ish) ones involve two drops:

The first and third will probably want to be done with MultiDropProcesses so that they can be triggered when both drops are in position. The second drop will call something like merge (which loses its identity) or mix (which waits to mix and get a drop out, which continues its path).

The first drop will initiate the process with something like mix in dir or mix and split with dir. This expects the other drop to be two steps in that direction and merges to the cell between them. To simplify getting good mixing, either of these can be followed by

To split, you need to specify the direction:

With future types, we might also want to allow something like become f, where f is a future drop, as a path. This would allow a single path to move around, merge with another that moves a bit, and then pick up later (and possibly elsewhere), when the other drop splits up.

Migrated from internal repository. Originally created by @EvanKirshenbaum on Jul 03, 2023 at 6:00 PM PDT.
EvanKirshenbaum commented 10 months ago

Writing up the work on futures (#283), I realized that for merge operations (and probably things like extractions and well entry), I should probably actually remove the drop from any variables that contain it. This would mean calling reset() on future drop variables and setting drop variables to MISSING. To track this, we'll need to keep track, probably in the drop, of all of the environment/variable combinations that contain it. This should probably be kept as a WeakKeyDictionary[Environment, list[str]] hung off the Drop. This will allow the environments to disappear if the drops outlive them.

Migrated from internal repository. Originally created by @EvanKirshenbaum on Jul 06, 2023 at 2:22 PM PDT.