Introduction ============ Welcome to pydfmux! There is a lot of new code here; this document should serve as a short overview of different aspects of the Python stack. You can find more technical descriptions in :doc:`object_model` and other documents linked from :doc:`../index`. .. contents:: Table of Contents :local: Hello World ----------- First, we'll create a reference to a single, :class:`pydfmux.Dfmux` object. .. doctest:: hello-world >>> import pydfmux >>> d = pydfmux.Dfmux(serial='004') >>> print d.serial 004 This :class:`pydfmux.Dfmux` resembles an ordinary Python object, but has two tricks up its sleeve: 1. It's actually a view into a database (managed by `SQLAlchemy `_), and 2. It's capable of communicating with hardware. In the following sections, we describe the benefits and impacts of each of these points. First, however, we introduce an alternative to describing hardware by writing Python code. Hardware Maps ------------- Above, we created a `Dfmux` object by instantiating it directly in Python. This approach is possible throughout the system, including for objects like `Bolometer`, `Wafer`, et cetera. However, an experiment is usually catalogued by a "hardware map" -- a set of text files (often version-controlled) that describe how an experiment is put together and how these connections evolve. In `pydfmux`, these files are written in YAML: .. doctest:: hello-yaml1 >>> import pydfmux >>> hwm = pydfmux.load_session(''' ... !HardwareMap ... - !Dfmux { serial: "004" } ... ''') >>> d = hwm.query(pydfmux.Dfmux).one() >>> print d.serial 004 This example has the same effect as the above code, except for a few important points: #. We created a `HardwareMap` object, which is an access point into the SQLAlchemy database allowing us to query any object within it. #. The contents of the `HardwareMap` were described in text, not in Python code. #. We retrieved a reference to our `Dfmux` object by *querying* the database. (The `one()` call ensures there was only a single result in the query, and returns it.) Unlike the Python frameworks in `pywtl`, `pydfmux` deeply embeds the hardware map into most of our code. As a result, Python code written at experiment scales (for example, tuning scripts) does not differ structurally from Python code targeting a single board on the benchtop (for example, firmware testing code.) Object Database --------------- Above, we created a `HardwareMap` object representing a database with a `Dfmux` object in it. This database can store many different kinds of objects, and follow links between them: .. testsetup:: hello-hwm1 >>> import pydfmux >>> hwm = pydfmux.load_session(''' ... !HardwareMap ... - !Dfmux { serial: "004" } ... ''') .. doctest:: hello-hwm1 >>> # Build a Wafer with three Bolometers attached >>> bs = [pydfmux.Bolometer(name='Frank'), ... pydfmux.Bolometer(name='Bernice'), ... pydfmux.Bolometer(name='Andy')] >>> w = pydfmux.Wafer(name='wafer1', bolometers=bs) >>> # Add these new objects to the hardware map (you don't need to worry >>> # about this, since you'd normally create Wafers/Bolometers in YAML.) >>> hwm.add(w) >>> hwm.commit() >>> # Search for all Wafer objects (and ensure there's only one) >>> wafer = hwm.query(pydfmux.Wafer).one() >>> # Inspect the Bolometers attached to this Wafer >>> wafer.bolometers.all() [Wafer(u'wafer1').Bolometer(u'Frank'), Wafer(u'wafer1').Bolometer(u'Bernice'), Wafer(u'wafer1').Bolometer(u'Andy')] >>> wafer.bolometer['Andy'] Wafer(u'wafer1').Bolometer(u'Andy') >>> wafer.bolometers.name [u'Frank', u'Bernice', u'Andy'] For a comprehensive list of objects (and a description of the linkages betweeen them), see :doc:`object_model`. Once again, you aren't intended to add hardware-map objects by writing Python code; for information on how to encode `Bolometer` and `Wafer` objects in YAML, see :doc:`yaml`. Talking to Remote Hardware -------------------------- So far, we've seen how pydfmux objects encode data (e.g. :code:`dfmux.serial`) and linkages (e.g. :code:`wafers.bolometer['Andy']`). We can also define and call *methods* on objects, in one of three ways: 1. by defining class methods directly, as you would in ordinary Python, 2. by using the :py:func:`@algorithm` or :py:func:`@macro` decorators (see :doc:`algorithms`), or 3. by calling methods defined by remote hardware on an IceBoard. This section describes how to use the third case. .. code-block:: python >>> import pydfmux >>> hwm = pydfmux.load_session(''' ... !HardwareMap ... - !Dfmux { serial: "004" } ... - !Dfmux { serial: "019" } ... ''') >>> dfmuxes = hwm.query(pydfmux.Dfmux) >>> print dfmuxes.get_fir_stage() [6, 6] This code instantiates 2 :class:`Dfmux` boards and places them into the hardware map. It then retrieves the Dfmuxes via a query, and issues :py:func:`Dfmux.get_fir_stage()` on both boards. Most importantly, the :py:func:`Dfmux.get_fir_stage()` call occurs on both boards *in parallel*. This is a trivial example, but in an experiment with 100 boards, repeatedly looping over each board in sequence does not scale. For information on how to simply and efficiently scale your code across large experiments, see :doc:`parallelizing`. .. vim: sts=3 ts=3 sw=3 tw=78 smarttab expandtab