qcsapphire


Nameqcsapphire JSON
Version 1.0.1 PyPI version JSON
download
home_page
SummaryA package for communicating with the Quantum Composer Sapphire 9200 TTL pulse generator.
upload_time2023-02-07 18:29:25
maintainer
docs_urlNone
author
requires_python>=3.8
licenseBSD 3-Clause License Copyright (c) 2022, University of Washington Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
keywords qt3 quantum composer sapphire ttl pulse generator
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Quantum Composer Sapphire 9200 Pulser Control

Helper code to communicate with [Quantum Composer's
Sapphire 9200 TTL pulse generator](https://www.quantumcomposers.com/_files/ugd/fe3f06_357ff95b25534660b8390c0305582a3f.pdf).

This code facilitates connections to the device, communication, error handling and system status.

## Installation

```
pip install qcsapphire
```

## Usage

### Determine the port


```python
import qcsapphire
qcsapphire.discover_devices()
```

Will return a list of ports and information about devices connected to those ports.
For example, on *nix platforms, you may see

```python
[['/dev/cu.BLTH', 'n/a', 'n/a'],
 ['/dev/cu.Bluetooth-Incoming-Port', 'n/a', 'n/a'],
 ['/dev/cu.usbmodem141101',
  'QC-Pulse Generator',
  'USB VID:PID=04D8:000A LOCATION=20-1.1']]
```

The device here is connected to `\dev\cu.usbmodem141101`.

On Windows you may see

```python
[['COM3',
  'Intel(R) Active Management Technology - SOL (COM3)',
  'PCI\\VEN_8086&DEV_43E3&SUBSYS_0A541028&REV_11\\3&11583659&1&B3'],
 ['COM5',
  'USB Serial Device (COM5)',
  'USB VID:PID=0483:A3E5 SER=206A36705430 LOCATION=1-2:x.0'],
 ['COM6',
  'USB Serial Device (COM6)',
  'USB VID:PID=04D8:000A SER= LOCATION=1-8:x.0'],
 ['COM7',
  'USB Serial Device (COM7)',
  'USB VID:PID=239A:8014 SER=3B0D07C25831555020312E341A3214FF LOCATION=1-6:x.0']]
  ```

It is certainly not obvious to which USB port the QC Sapphire is connected. However,
using the Windows Task Manager, as well as trial and error, should eventually
reveal the correct serial port to use. In this case, `COM6`.

### Connection to Pulser

```python
pulser = qcsapphire.Pulser('COM6')
```

### Communication

For normal usage, all commands sent to the device should use the `query()` method or
with property-like calls (see section below).

The `query()` method will write a command, read the response from the device,
check for errors (raising an Exception when an error is found) and return the string
response. For example,

```python
ret_val = pulser.query(':PULSE0:STATE?')
print(ret_val)
'0'
```

```python
ret_val = pulser.query(':PULSE1:WIDTH 0.000025')
print(ret_val)
'ok'

ret_val = pulser.query(':PULSE1:WIDTH?')
print(ret_val)
'0.000025000'
```

#### Property-Like Calls

It's possible to make the same calls to the pulser using a property-like call.
Instead of calling `pulser.query("command1:subcommand:subsubcommand value")`,
one can call `pulser.command1.subcommand.subsubcommand(value)`, which is more readable.

For example,

```python
ret_val = pulser.pulse1.width(0.000025) #sets the width of channel A
print(ret_val) # 'ok'

width = pulser.pulse1.width() #asks for the width of channel A
print(width) # '0.000025000'
```

All of the SCPI commands can be built this way.

In either case, the user is responsible
for sending the correct command strings by following
[the API](https://www.quantumcomposers.com/_files/ugd/fe3f06_357ff95b25534660b8390c0305582a3f.pdf).
It should be pointed out there is no need to worry about string encoding and carriage returns / line feeds,
as that is taken care of by the code.

#### Programming Channels

Instead of using 'pulseN' to access a particular QCSapphire channel, methods have
been added to facilitate more programmatic ways of control.

```

```


### Global and Channel States

Two methods exist to report on global and channel settings

##### Global Settings

```python
import pprint
pp = pprint.PrettyPrinter(indent=4)

pp.pprint(pulser.report_global_settings())
```

##### Channel Settings

```python
for channel in range(1,5):
    pp.pprint(f'channel {channel}')
    pp.pprint(pulser.report_channel_settings(channel))
```

### Examples

The following examples demonstrate both simple and advanced usage.

#### Resets

The QCSapphire can be unstable at times. We have found that forcing the
system to reset, multiple times, is necessary to program the pulser the
the ways described below. You may or may-not have better stability
if you reset the pulser before programming.


```python
for i in range(2):
      pulser.set_all_state_off()
      time.sleep(1)
pulser.query('*RST')
pulser.system.mode('normal')
```

#### CWODMR

In CWODMR our spin system is continuously illuminated with an optical pump,
while an oscillating (RF) magnetic field drives magnetic transitions between
spin states. While the optical pump is continuously on, we can use the QCSapphire
to control the RF magnetic signal timing. The RF signal hardware will control
the frequency of the field (for NV centers, between 2.6 and 3.2 GHz, depending on
the level of Zeeman splitting). The QCSapphire's TTL output may operate a
switch that blocks the RF signal at known times. This allows the experimenter
to acquire photo luminescence (PL) signal for periods when no RF signal is applied
in order to measure a background, and to then measure PL when the RF is applied.

In this case, we wish to program the QCSapphire to output a TTL signal of fixed
duration and period. The following example shows how to generate a 5 microsecond on/off TTL signal.

##### Simple Example: Single Channel

In the simple case, we just have a single channel ('B') that we wish to use to
generate a square wave. The documentation is straight-forward here. NB: channel
'B' is demonstrated here because we use channel 'A' to control the optical
pumping in other experiments (pulsed-ODMR, Rabi, etc.).

```python
total_width = 10e-6 #in seconds
pulser.system.period(total_width)

#use channel B for RF switch and use 'normal' mode
channel = pulser.channel('B')
channel.mode('normal')

#create a 5 microsecond pulse
#round to nearest 10ns bc QCSapphire does not behave well with machine errors
channel.width(np.round(total_width/2, 8))
channel.delay(0)

pulser.system.state(1) #start the pulser
```

##### Realistic Example: Adding Clock and Trigger Channels

In order to take robust data, however, we need to control our data acquisition
system (DAQ). Supplying an external clock and trigger signal from the QCSapphire ensures
that the DAQ and pulser do not drift from each other and data is acquired at the
exact moment the system is ready.

However, this changes how we must program the RF on/off to be 5 microseconds in
width. The following steps through the necessary calls to produce a

  * 200 ns clock period
  * 5 microsecond on/off RF pulse
  * single trigger pulse at the start of every 10 microsecond period

###### First the Clock

In this example, channel ('C') is programmed to act as the clock. The
pulser system clock period is set to the smallest allowed value in order to
set our clock tick leading edge period to 200 ns.
This channel is programmed exactly as the simple case above with a smaller width.

```python
clock_period = 200e-9
pulser.system.period(clock_period)
channel = pulser.channel('C')
channel.mode('normal')
channel.width(np.round(clock_period/2, 8)) #100 ns wide square wave
channel.delay(0)
```

###### Add RF switch

A 5 microsecond wide pulse (positive), followed by
5 microseconds off on channel B is implemented using the duty-cycle mode.
In the duty-cycle mode, we specify the number of `clock_periods` ON we wish for the
channel to generate a signal we describe. We then specify how many `clock_periods`
OFF until the pattern repeats. In this case channel B is programmed to output
a 5 microsecond wide pulse ONE time and then wait the appropriate number of
`clock_periods` such that the start of the next 5 microsecond wide pulse occurs
10 microseconds later. Thus, the OFF duty cycle will be `10 mu*s / clock_period  - 1`


```python
rf_pulse_width = 5e-6
full_pulse_sequence_period = 2 * rf_pulse_width
n_on_cycles = 1
n_off_cycles = np.round(full_cycle_width/clock_period).astype(int) - n_on_cycles
channel = pulser.channel('B')
channel.mode('dcycle')
channel.width(np.round(rf_pulse_width, 8)) #5 mu*s wide square wave
channel.delay(0)
channel.pcounter(n_on_cycles)
channel.ocounter(n_off_cycles)
```

When data are acquired, the first 5 microseconds will be the 'signal' where
we expect a dip in PL, and the second 5 microseconds will be the 'background'
where no RF signal is present and we expect full PL intensity.

###### Add Trigger

We now want to produce a single square wave output on channel D at the beginning of
each 10 microsecond period. This will be used to trigger our DAQ. Again, we
use the duty cycle mode.

```python
trigger_width = 100e-9
n_on_cycles = 1
n_off_cycles = np.round(full_pulse_sequence_period/clock_period).astype(int) - n_on_cycles
channel = pulser.channel('D')
channel.mode('dcycle')
channel.width(np.round(trigger_width, 8)) #100 ns wide square wave
channel.delay(0)
channel.pcounter(n_on_cycles)
channel.ocounter(n_off_cycles)
```

###### Set the Channel States

We finally set the states of the channels and system to start the sequence.

```python
pulser.channel('B').state(1)
pulser.channel('C').state(1)
pulser.channel('D').state(1)
pulser.system.state(1)
```

#### Rabi Oscillation / pulsed-ODRM Programming

In pulsed-ODMR, one separates the optical pumping from the application of
the RF magnetic field signal. In a Rabi oscillation experiment, the
width of the RF signal is varied as the PL contrast to background is measured.
Both of these pulse sequences are similar to each other and both are more complicated than the example
above.

The main difference between the CWODMR pulses above and Rabi/pulse-ODMR sequences is
that an appropriate delay to RF pulse is added and channel A is used to
control the optical pump/readout. The RF pulse must come after the initial optical pump.

The full sequence is an optical pump signal (~5 microseconds), followed by an RF
signal of some duration, followed by an optical readout (~5 microseconds),
followed by some period with no RF or optical pump. A description of this sequence, though done
with a lock-in amplifier, is found [here](https://aapt.scitation.org/doi/full/10.1119/10.0001905).

However, the duty-cycle mode is used. The RF channel (B) is programmed to generate
a delayed signal, of some width, ONE time, and then wait M clock cycles before
the next signal, where M = `full_pulse_sequence_period / clock_period - 1`.

An example of this sequence is found in the [QT3 lab experimental code](https://github.com/gadamc/qt3-utils/blob/b03050d86c5116a21986278734817be39df2da8a/src/qt3utils/experiments/rabi.py#L164).

Additionally, as can be seen in the code above, some extra delay after the optical
pump was added because of a finite hardware response time of ~700-900 ns.


#### Trigger Pulses on Channel A via Software Trigger

Here's an example of using an external trigger to generate an output pulse from
the QCSapphire. This may be very useful when used in combination with a
device that has more output channels and more sophisticated pulse sequeunce
programming, such as the Spin Core Pulse Blaster. One of the Pulse Blaster's
output channels could be used to trigger the external trigger channel of the QCSapphire.

One reason to do this would be to utilize QCSapphire's superior pulse width
resolution. The smallest square wave from the Pulse Blaster is 50 ns, while
the QCSapphire can produce a 10 ns wide pulse (according to its documentation)

```python

### TODO -- check this code! It doesn't look right.
pulser.channel('A').mode('single')
#pulser.channel('A').cmode('single') #do we need cmode instead of mode?

pulser.channel('A').period(0.2) #200 ms system pulse
pulser.channel('A').external.mode('trigger')
pulser.channel('A').width(10e-9) #10 ns wide pulse

#pulser.channel('B').cmode('single')
#pulser.channel('B').polarity('normal')
#pulser.channel('B').width(10e-9) #10 ns wide pulse
#pulser.channel('B').state(0)
pulser.channel('A').state(0)


#trigger loop example

#wait N seconds between triggers
wait_N = 5.0
N_pulses = 50

#activate pulser and output channel
pulser.channel('A').state(1)
#pulser.channel('B').state(1)

for i in range(N_pulses):
    pulser.software_trigger() #trigger the pulser
    time.sleep(wait_N)

#deactivate
pulser.channel('A').state(0)
#pulser.channel('B').state(0)
```


### Debugging

If you hit an error, especially when trying to use the property-like calls,
the last string written to the Serial port is found in the
`.last_write_command` attribute of the pulser object.

```python
pulser.channle('B').width(25e-6)
print(pulser.last_write_command)
# ':PULSE1:WIDTH 2.5e-05'
```

Additionally, you can see the recent command history of the object (last 1000 commands)

```python
for command in pulser.command_history():
  print(command)
```

# LICENSE

[LICENCE](LICENSE)

##### Acknowledgments

The `Property` class of this code was taken from `easy-scpi`: https://github.com/bicarlsen/easy-scpi
and modified.

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "qcsapphire",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "\"G. Adam Cox\" <gadamc@gmail.com>, Maxwell Parsons <mfpars@uw.edu>",
    "keywords": "qt3,quantum composer,sapphire,ttl,pulse generator",
    "author": "",
    "author_email": "\"G. Adam Cox\" <gadamc@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/2a/e7/2684abe78a051dcc748f73b93f893ff00d15b9a214725db83ea62d65dce3/qcsapphire-1.0.1.tar.gz",
    "platform": null,
    "description": "# Quantum Composer Sapphire 9200 Pulser Control\n\nHelper code to communicate with [Quantum Composer's\nSapphire 9200 TTL pulse generator](https://www.quantumcomposers.com/_files/ugd/fe3f06_357ff95b25534660b8390c0305582a3f.pdf).\n\nThis code facilitates connections to the device, communication, error handling and system status.\n\n## Installation\n\n```\npip install qcsapphire\n```\n\n## Usage\n\n### Determine the port\n\n\n```python\nimport qcsapphire\nqcsapphire.discover_devices()\n```\n\nWill return a list of ports and information about devices connected to those ports.\nFor example, on *nix platforms, you may see\n\n```python\n[['/dev/cu.BLTH', 'n/a', 'n/a'],\n ['/dev/cu.Bluetooth-Incoming-Port', 'n/a', 'n/a'],\n ['/dev/cu.usbmodem141101',\n  'QC-Pulse Generator',\n  'USB VID:PID=04D8:000A LOCATION=20-1.1']]\n```\n\nThe device here is connected to `\\dev\\cu.usbmodem141101`.\n\nOn Windows you may see\n\n```python\n[['COM3',\n  'Intel(R) Active Management Technology - SOL (COM3)',\n  'PCI\\\\VEN_8086&DEV_43E3&SUBSYS_0A541028&REV_11\\\\3&11583659&1&B3'],\n ['COM5',\n  'USB Serial Device (COM5)',\n  'USB VID:PID=0483:A3E5 SER=206A36705430 LOCATION=1-2:x.0'],\n ['COM6',\n  'USB Serial Device (COM6)',\n  'USB VID:PID=04D8:000A SER= LOCATION=1-8:x.0'],\n ['COM7',\n  'USB Serial Device (COM7)',\n  'USB VID:PID=239A:8014 SER=3B0D07C25831555020312E341A3214FF LOCATION=1-6:x.0']]\n  ```\n\nIt is certainly not obvious to which USB port the QC Sapphire is connected. However,\nusing the Windows Task Manager, as well as trial and error, should eventually\nreveal the correct serial port to use. In this case, `COM6`.\n\n### Connection to Pulser\n\n```python\npulser = qcsapphire.Pulser('COM6')\n```\n\n### Communication\n\nFor normal usage, all commands sent to the device should use the `query()` method or\nwith property-like calls (see section below).\n\nThe `query()` method will write a command, read the response from the device,\ncheck for errors (raising an Exception when an error is found) and return the string\nresponse. For example,\n\n```python\nret_val = pulser.query(':PULSE0:STATE?')\nprint(ret_val)\n'0'\n```\n\n```python\nret_val = pulser.query(':PULSE1:WIDTH 0.000025')\nprint(ret_val)\n'ok'\n\nret_val = pulser.query(':PULSE1:WIDTH?')\nprint(ret_val)\n'0.000025000'\n```\n\n#### Property-Like Calls\n\nIt's possible to make the same calls to the pulser using a property-like call.\nInstead of calling `pulser.query(\"command1:subcommand:subsubcommand value\")`,\none can call `pulser.command1.subcommand.subsubcommand(value)`, which is more readable.\n\nFor example,\n\n```python\nret_val = pulser.pulse1.width(0.000025) #sets the width of channel A\nprint(ret_val) # 'ok'\n\nwidth = pulser.pulse1.width() #asks for the width of channel A\nprint(width) # '0.000025000'\n```\n\nAll of the SCPI commands can be built this way.\n\nIn either case, the user is responsible\nfor sending the correct command strings by following\n[the API](https://www.quantumcomposers.com/_files/ugd/fe3f06_357ff95b25534660b8390c0305582a3f.pdf).\nIt should be pointed out there is no need to worry about string encoding and carriage returns / line feeds,\nas that is taken care of by the code.\n\n#### Programming Channels\n\nInstead of using 'pulseN' to access a particular QCSapphire channel, methods have\nbeen added to facilitate more programmatic ways of control.\n\n```\n\n```\n\n\n### Global and Channel States\n\nTwo methods exist to report on global and channel settings\n\n##### Global Settings\n\n```python\nimport pprint\npp = pprint.PrettyPrinter(indent=4)\n\npp.pprint(pulser.report_global_settings())\n```\n\n##### Channel Settings\n\n```python\nfor channel in range(1,5):\n    pp.pprint(f'channel {channel}')\n    pp.pprint(pulser.report_channel_settings(channel))\n```\n\n### Examples\n\nThe following examples demonstrate both simple and advanced usage.\n\n#### Resets\n\nThe QCSapphire can be unstable at times. We have found that forcing the\nsystem to reset, multiple times, is necessary to program the pulser the\nthe ways described below. You may or may-not have better stability\nif you reset the pulser before programming.\n\n\n```python\nfor i in range(2):\n      pulser.set_all_state_off()\n      time.sleep(1)\npulser.query('*RST')\npulser.system.mode('normal')\n```\n\n#### CWODMR\n\nIn CWODMR our spin system is continuously illuminated with an optical pump,\nwhile an oscillating (RF) magnetic field drives magnetic transitions between\nspin states. While the optical pump is continuously on, we can use the QCSapphire\nto control the RF magnetic signal timing. The RF signal hardware will control\nthe frequency of the field (for NV centers, between 2.6 and 3.2 GHz, depending on\nthe level of Zeeman splitting). The QCSapphire's TTL output may operate a\nswitch that blocks the RF signal at known times. This allows the experimenter\nto acquire photo luminescence (PL) signal for periods when no RF signal is applied\nin order to measure a background, and to then measure PL when the RF is applied.\n\nIn this case, we wish to program the QCSapphire to output a TTL signal of fixed\nduration and period. The following example shows how to generate a 5 microsecond on/off TTL signal.\n\n##### Simple Example: Single Channel\n\nIn the simple case, we just have a single channel ('B') that we wish to use to\ngenerate a square wave. The documentation is straight-forward here. NB: channel\n'B' is demonstrated here because we use channel 'A' to control the optical\npumping in other experiments (pulsed-ODMR, Rabi, etc.).\n\n```python\ntotal_width = 10e-6 #in seconds\npulser.system.period(total_width)\n\n#use channel B for RF switch and use 'normal' mode\nchannel = pulser.channel('B')\nchannel.mode('normal')\n\n#create a 5 microsecond pulse\n#round to nearest 10ns bc QCSapphire does not behave well with machine errors\nchannel.width(np.round(total_width/2, 8))\nchannel.delay(0)\n\npulser.system.state(1) #start the pulser\n```\n\n##### Realistic Example: Adding Clock and Trigger Channels\n\nIn order to take robust data, however, we need to control our data acquisition\nsystem (DAQ). Supplying an external clock and trigger signal from the QCSapphire ensures\nthat the DAQ and pulser do not drift from each other and data is acquired at the\nexact moment the system is ready.\n\nHowever, this changes how we must program the RF on/off to be 5 microseconds in\nwidth. The following steps through the necessary calls to produce a\n\n  * 200 ns clock period\n  * 5 microsecond on/off RF pulse\n  * single trigger pulse at the start of every 10 microsecond period\n\n###### First the Clock\n\nIn this example, channel ('C') is programmed to act as the clock. The\npulser system clock period is set to the smallest allowed value in order to\nset our clock tick leading edge period to 200 ns.\nThis channel is programmed exactly as the simple case above with a smaller width.\n\n```python\nclock_period = 200e-9\npulser.system.period(clock_period)\nchannel = pulser.channel('C')\nchannel.mode('normal')\nchannel.width(np.round(clock_period/2, 8)) #100 ns wide square wave\nchannel.delay(0)\n```\n\n###### Add RF switch\n\nA 5 microsecond wide pulse (positive), followed by\n5 microseconds off on channel B is implemented using the duty-cycle mode.\nIn the duty-cycle mode, we specify the number of `clock_periods` ON we wish for the\nchannel to generate a signal we describe. We then specify how many `clock_periods`\nOFF until the pattern repeats. In this case channel B is programmed to output\na 5 microsecond wide pulse ONE time and then wait the appropriate number of\n`clock_periods` such that the start of the next 5 microsecond wide pulse occurs\n10 microseconds later. Thus, the OFF duty cycle will be `10 mu*s / clock_period  - 1`\n\n\n```python\nrf_pulse_width = 5e-6\nfull_pulse_sequence_period = 2 * rf_pulse_width\nn_on_cycles = 1\nn_off_cycles = np.round(full_cycle_width/clock_period).astype(int) - n_on_cycles\nchannel = pulser.channel('B')\nchannel.mode('dcycle')\nchannel.width(np.round(rf_pulse_width, 8)) #5 mu*s wide square wave\nchannel.delay(0)\nchannel.pcounter(n_on_cycles)\nchannel.ocounter(n_off_cycles)\n```\n\nWhen data are acquired, the first 5 microseconds will be the 'signal' where\nwe expect a dip in PL, and the second 5 microseconds will be the 'background'\nwhere no RF signal is present and we expect full PL intensity.\n\n###### Add Trigger\n\nWe now want to produce a single square wave output on channel D at the beginning of\neach 10 microsecond period. This will be used to trigger our DAQ. Again, we\nuse the duty cycle mode.\n\n```python\ntrigger_width = 100e-9\nn_on_cycles = 1\nn_off_cycles = np.round(full_pulse_sequence_period/clock_period).astype(int) - n_on_cycles\nchannel = pulser.channel('D')\nchannel.mode('dcycle')\nchannel.width(np.round(trigger_width, 8)) #100 ns wide square wave\nchannel.delay(0)\nchannel.pcounter(n_on_cycles)\nchannel.ocounter(n_off_cycles)\n```\n\n###### Set the Channel States\n\nWe finally set the states of the channels and system to start the sequence.\n\n```python\npulser.channel('B').state(1)\npulser.channel('C').state(1)\npulser.channel('D').state(1)\npulser.system.state(1)\n```\n\n#### Rabi Oscillation / pulsed-ODRM Programming\n\nIn pulsed-ODMR, one separates the optical pumping from the application of\nthe RF magnetic field signal. In a Rabi oscillation experiment, the\nwidth of the RF signal is varied as the PL contrast to background is measured.\nBoth of these pulse sequences are similar to each other and both are more complicated than the example\nabove.\n\nThe main difference between the CWODMR pulses above and Rabi/pulse-ODMR sequences is\nthat an appropriate delay to RF pulse is added and channel A is used to\ncontrol the optical pump/readout. The RF pulse must come after the initial optical pump.\n\nThe full sequence is an optical pump signal (~5 microseconds), followed by an RF\nsignal of some duration, followed by an optical readout (~5 microseconds),\nfollowed by some period with no RF or optical pump. A description of this sequence, though done\nwith a lock-in amplifier, is found [here](https://aapt.scitation.org/doi/full/10.1119/10.0001905).\n\nHowever, the duty-cycle mode is used. The RF channel (B) is programmed to generate\na delayed signal, of some width, ONE time, and then wait M clock cycles before\nthe next signal, where M = `full_pulse_sequence_period / clock_period - 1`.\n\nAn example of this sequence is found in the [QT3 lab experimental code](https://github.com/gadamc/qt3-utils/blob/b03050d86c5116a21986278734817be39df2da8a/src/qt3utils/experiments/rabi.py#L164).\n\nAdditionally, as can be seen in the code above, some extra delay after the optical\npump was added because of a finite hardware response time of ~700-900 ns.\n\n\n#### Trigger Pulses on Channel A via Software Trigger\n\nHere's an example of using an external trigger to generate an output pulse from\nthe QCSapphire. This may be very useful when used in combination with a\ndevice that has more output channels and more sophisticated pulse sequeunce\nprogramming, such as the Spin Core Pulse Blaster. One of the Pulse Blaster's\noutput channels could be used to trigger the external trigger channel of the QCSapphire.\n\nOne reason to do this would be to utilize QCSapphire's superior pulse width\nresolution. The smallest square wave from the Pulse Blaster is 50 ns, while\nthe QCSapphire can produce a 10 ns wide pulse (according to its documentation)\n\n```python\n\n### TODO -- check this code! It doesn't look right.\npulser.channel('A').mode('single')\n#pulser.channel('A').cmode('single') #do we need cmode instead of mode?\n\npulser.channel('A').period(0.2) #200 ms system pulse\npulser.channel('A').external.mode('trigger')\npulser.channel('A').width(10e-9) #10 ns wide pulse\n\n#pulser.channel('B').cmode('single')\n#pulser.channel('B').polarity('normal')\n#pulser.channel('B').width(10e-9) #10 ns wide pulse\n#pulser.channel('B').state(0)\npulser.channel('A').state(0)\n\n\n#trigger loop example\n\n#wait N seconds between triggers\nwait_N = 5.0\nN_pulses = 50\n\n#activate pulser and output channel\npulser.channel('A').state(1)\n#pulser.channel('B').state(1)\n\nfor i in range(N_pulses):\n    pulser.software_trigger() #trigger the pulser\n    time.sleep(wait_N)\n\n#deactivate\npulser.channel('A').state(0)\n#pulser.channel('B').state(0)\n```\n\n\n### Debugging\n\nIf you hit an error, especially when trying to use the property-like calls,\nthe last string written to the Serial port is found in the\n`.last_write_command` attribute of the pulser object.\n\n```python\npulser.channle('B').width(25e-6)\nprint(pulser.last_write_command)\n# ':PULSE1:WIDTH 2.5e-05'\n```\n\nAdditionally, you can see the recent command history of the object (last 1000 commands)\n\n```python\nfor command in pulser.command_history():\n  print(command)\n```\n\n# LICENSE\n\n[LICENCE](LICENSE)\n\n##### Acknowledgments\n\nThe `Property` class of this code was taken from `easy-scpi`: https://github.com/bicarlsen/easy-scpi\nand modified.\n",
    "bugtrack_url": null,
    "license": "BSD 3-Clause License  Copyright (c) 2022, University of Washington  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ",
    "summary": "A package for communicating with the Quantum Composer Sapphire 9200 TTL pulse generator.",
    "version": "1.0.1",
    "split_keywords": [
        "qt3",
        "quantum composer",
        "sapphire",
        "ttl",
        "pulse generator"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9bd3bad6495b6a6aae09a811f4c5589238f076beff9eb17cc7e228530b847603",
                "md5": "815a228d5ea7155e39767666c35a5fa3",
                "sha256": "552c61db30efc49cbdbde39dc8de9b667a26fd525c13bf4e5bb13a8786c50dfd"
            },
            "downloads": -1,
            "filename": "qcsapphire-1.0.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "815a228d5ea7155e39767666c35a5fa3",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 11328,
            "upload_time": "2023-02-07T18:29:23",
            "upload_time_iso_8601": "2023-02-07T18:29:23.794369Z",
            "url": "https://files.pythonhosted.org/packages/9b/d3/bad6495b6a6aae09a811f4c5589238f076beff9eb17cc7e228530b847603/qcsapphire-1.0.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2ae72684abe78a051dcc748f73b93f893ff00d15b9a214725db83ea62d65dce3",
                "md5": "51bc15d38467bc758f7b7297cd14f71a",
                "sha256": "f665e56d0d65e23ab9ae5ae0e6b4e658a9b426d95f8ed95218cdf3c4a31165f4"
            },
            "downloads": -1,
            "filename": "qcsapphire-1.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "51bc15d38467bc758f7b7297cd14f71a",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 16166,
            "upload_time": "2023-02-07T18:29:25",
            "upload_time_iso_8601": "2023-02-07T18:29:25.128259Z",
            "url": "https://files.pythonhosted.org/packages/2a/e7/2684abe78a051dcc748f73b93f893ff00d15b9a214725db83ea62d65dce3/qcsapphire-1.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-02-07 18:29:25",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "lcname": "qcsapphire"
}
        
Elapsed time: 0.03727s