HPInc / HP-Digital-Microfluidics

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

Allow per-dimension `is_close_to()` absolute tolerances #210

Open EvanKirshenbaum opened 8 months ago

EvanKirshenbaum commented 8 months ago

While trying to figure out why the combinatorial synthesis protocol was hanging at the end when using the Opentrons (#82), I realized (0ec975e687f636e0f761e207e63f178e481e9590) that the problem was that due to representation error (probably on the initial transfer into one of the wells), the last transferred amount was very slightly less than the 1 drop that was requested, which led to it never being considered complete.

The obvious solution was to use is_close_to() to check whether we've essentially got what we want, but my first try:

            want = first.volume
            if want.is_close_to(v):
                xfers.popleft()
                self.enqueue_finished(first, r, want)
                break

didn't actually work. The problem is that is_close_to() is defined as

    def is_close_to(self, other: ZeroOr[D], *, 
                    rel_tol: float = 1e-09, 
                    abs_tol: Optional[ZeroOr[D]] = None,
                    ) -> bool:
        my_mag = self.magnitude
        their_mag = self._magnitude_of(other, "is_close_to")
        tol = self._magnitude_of(abs_tol, "is_close_to.abs_tol") if abs_tol is not None else 1e-9 if their_mag==0 else 0
        if abs(my_mag-their_mag) < tol:
            return True
        return math.isclose(self.magnitude, their_mag, rel_tol=rel_tol)

If the absolute tolerance (abs_tol) isn't specified, it defaults to 1e-9 if the target is zero and zero (i.e., essentially ignored), otherwise. To fix this, in the Opentrons code, I added abs_tol=0.01*uL, which worked.

But it wouldn't have been right even if I had been comparing against zero (as the amount remaining), because the default absolute tolerance of 1e-9 is in terms of the base unit, which for volume is cubic meters, and that's an entire microliter, which is too big for the application.

It dawned on me that I should add a tolerance to Dimensionality, in much the same way that I currently have default_units. The the user could say something like

Volume.tolerance = 0.01*uL

and this tolerance could be used by is_close_to() when an absolute tolerance isn't specified.

If we do this, it should also be specifiable via a command-line argument (a la --units), and probably as an interactive variable in the macro language, especially if equality is redefined to be is_close_to (or a new operator is added) (#211).

Migrated from internal repository. Originally created by @EvanKirshenbaum on Oct 26, 2022 at 11:43 AM PDT.