2020-07-28 • Storing values in base units

2020-07-28 • Storing values in base units

..but specifying and displaying them in physiological / most natural units

unyt.convert_to_base() is slow. So we don’t want to call this in our processing functions.

Solution: store all units in base units throughout calculation, and only do conversion at user interaction time. That is, when:

  • defining parameters

  • printing values

  • plotting

This notebook prototypes this solution.

(Alternative solutions we don’t explore here:

  • use Brian, which has its own unit system builtin.

  • program in Julia

).

Note

The codebase now contains better documented versions of the code below. (See the units.unyt_mod module).

Prototyping

%run init.ipynb
class Array(unyt_array):
    def __new__(cls, *args, **kwargs):
        obj = super().__new__(cls, *args, **kwargs)
        obj.display_units = obj.units
        if not obj.units.is_dimensionless:
            obj = obj.astype('float64')
            obj.convert_to_base('mks')
        return obj
    
    def __array_finalize__(self, obj):
        super().__array_finalize__(obj)
        self.display_units = getattr(obj, "display_units", None)
    
    @property
    def in_display_units(self):
        if self.units.is_dimensionless:
            return unyt_array(self)
        else:
            # Note that we can't return `self.to(self.display_units)` here, as that
            # would convert to base units again (in __new__).
            return unyt_array(self).to(self.display_units)

    def __repr__(self):
        clsname = self.__class__.__name__
        if self.units.is_dimensionless:
            return f"{clsname}({str(self)}, {self.dtype})"
        else:
            return f"{clsname}({str(self)}, stored in {self.units}, {self.dtype})"
    
    def __str__(self):
        if self.units.is_dimensionless:
            return f"{self.value} (dimensionless)"
        else:
            return str(self.in_display_units)

    
class Quantity(Array, unyt_quantity):
    ...

a = Array([1.0,2,3], mV, name="Potential")

q = Quantity(1, mV/mV)
q
Quantity(1 (dimensionless), int32)
print(q)
1 (dimensionless)
a
Array([1 2 3] mV, stored in V, float64)
print(a)
[1 2 3] mV
from unyt.mpl_interface import unyt_arrayConverter
from matplotlib.units import registry, ConversionInterface

class MyConversionInterface(unyt_arrayConverter):
    def __new__(cls):
        return super(ConversionInterface, cls).__new__(cls)
    
    @staticmethod
    def default_units(x: Array, axis):
        unyt_arrayConverter().default_units(x, axis)
        return x.display_units
    
    @staticmethod
    def convert(value: Array, unit, axis):
        return unyt_arrayConverter().convert(
            value.in_display_units, unit, axis=axis)
        
registry[Array] = MyConversionInterface()

plt.plot(a);
../_images/2020-07-28__Storing_in_base_units_8_0.png
plt.plot(a, yunits=V);
../_images/2020-07-28__Storing_in_base_units_9_0.png
class Unit(unyt.Unit):
    def __mul__(self, other):
        obj = super().__mul__(other)
        if isinstance(obj, unyt_quantity):
            return Array(obj)
        elif isinstance(obj, unyt_array):
            return Quantity(obj)
        else:
            return obj

mV = Unit('mV')
a = 4 * mV
a
Array(4.0 mV, stored in V, float64)
a.ndarray_view() == a.ndview == a.d
True
a.ndview
array(0.004)

It all works nicely :)

I will now integrate this code in the codebase.

New codebase test

%run init.ipynb
Importing np, mpl, plt … ✔
Importing unyt … ✔
Importing code package (voltage_to_wiring_sim), as `v` … ✔
Imported * from v.units
v.neuron_sim.test()
Simulations with and without units yield equal results.
../_images/2020-07-28__Storing_in_base_units_17_1.png
print(tg)
TimeGrid
--------
T = 1000.0 ms
dt = 0.1 ms
N = 10000
t = [0 0.1 0.2 ... 999.7 999.8 999.9] ms
sim = izh_neuron(tg, RS, I_e=np.ones(tg.N) * 80 * pA)
print(sim)
SimResult
---------
V_m = [-60 -59.92 -59.84 ... -53.76 -53.75 -53.74] mV
u = [0 0 -0.00048 ... 10 9.933 9.866] pA
I_syn = [-0 -0 -0 ... -0 -0 -0] pA
sim.V_m[:3]
Array([-60 -59.92 -59.84] mV, stored in V, float64)
sim.V_m.value[:3]
array([-0.06, -0.05992, -0.05984])

👍

plt.plot(tg.t, sim.V_m);
../_images/2020-07-28__Storing_in_base_units_23_0.png
plt.plot(tg.t, sim.u);
../_images/2020-07-28__Storing_in_base_units_24_0.png
v.presynaptic_spike_train.show()
../_images/2020-07-28__Storing_in_base_units_25_0.png
%%time
sts = v.presynaptic_spike_train.generate_spike_trains(500)
Wall time: 78.8 ms
%%time
sim = izh_neuron(
    v.time_grid.TimeGrid(10*min, dt=0.1*ms),
    params=RS,
    I_e=np.ones(tg.N) * 80 * pA
)
Wall time: 682 ms