diff --git a/.github/workflows/darker.yaml b/.github/workflows/darker.yaml new file mode 100644 index 00000000..e46b225f --- /dev/null +++ b/.github/workflows/darker.yaml @@ -0,0 +1,16 @@ +name: Lint with Darker + +on: [push, pull_request] + +jobs: + lint-with-darker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3.0.2 + with: + fetch-depth: 0 + - uses: akaihola/darker@1.5.0 + with: + options: "--check --diff" + src: "./broadbean" + revision: "origin/master..." diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 3025e7b1..9b1e9512 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -73,12 +73,14 @@ jobs: - name: Build docs on linux run: | cd docs - make -f Makefile execute + export SPHINXOPTS="-W -v" + make html if: runner.os == 'Linux' - name: Build docs on windows run: | cd docs - ./execute_notebooks.cmd + $env:SPHINXOPTS = "-W -v" + ./make.bat html if: runner.os == 'Windows' - name: Upload build docs uses: actions/upload-artifact@v3.1.0 diff --git a/.gitignore b/.gitignore index 87666685..a48eb443 100644 --- a/.gitignore +++ b/.gitignore @@ -149,4 +149,4 @@ cython_debug/ .vscode/ # Mac files -.DS_Store \ No newline at end of file +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..2c2dbfde --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-ast + - id: check-json + - id: check-toml + - id: check-yaml + - id: debug-statements + - id: mixed-line-ending + args: ['--fix=no'] + - repo: https://github.com/asottile/pyupgrade + rev: v2.32.1 + hooks: + - id: pyupgrade + args: ['--py37-plus', '--keep-runtime-typing'] + - repo: https://github.com/akaihola/darker + rev: 1.5.0 + hooks: + - id: darker + args: [-i] + additional_dependencies: [isort] diff --git a/README.md b/README.md index c8233c60..6e22474e 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ [ ![PyPI python versions](https://img.shields.io/pypi/pyversions/broadbean.svg) ](https://pypi.python.org/pypi/broadbean/) [ ![Build Status Github](https://github.com/QCoDeS/broadbean/workflows/Run%20mypy%20and%20pytest/badge.svg) ](https://github.com/QCoDeS/broadbean/actions?query=workflow%3A%22Run+mypy+and+pytest%22) -A library for making pulses. Supposed to be used with QCoDeS (in -particular its Tektronix AWG 5014 driver), but works as standalone. +A library for making pulses that can be leveraged with QCoDeS (in +particular its Tektronix AWG 5014 driver), but also works as standalone. -The usage is documented in the jupyter notebooks found in the `docs` folder. +The usage is documented in example notebooks TODO: add link to hosted docs. Short description: The broadbean module lets the user compose and manipulate pulse sequences. The aim of the module is to reduce pulse @@ -26,37 +26,8 @@ The name: The broad bean is one of my favourite pulses. ### Formal requirements -The broadbean package only works with python 3.6+ +The broadbean package only works with python 3.7+ ### Installation -On a good day, installation is as easy as -``` -$ git clone https://github.com/QCoDeS/broadbean.git bbdir -$ cd bbdir -$ pip install . -``` -behind the scenes, `numpy`, `matplotlib`, and `PyQt5` are installed if -not found. If `pip` failed you, you may need to run it as root. But a -better idea is to use a [virtual enviroment](https://github.com/pyenv/pyenv-virtualenv). - -You can now fire up a python 3 interpreter and go -``` ->>> import broadbean as bb ->>> from broadbean import ripasso as rp -``` - -### Documentation - -Apart from the example notebooks, auto-generated documentation is -available. As for now, the user must built it herself, but that is -luckily easy. - -In the `bbdir` folder, do: -``` -$ pip install -r docs_requirements.txt -$ cd docs -$ make html -``` -then ignore all warnings and just have a look at the file `bbdir/docs/build/html/index.html`. - +TODO: add link to installation from docs diff --git a/broadbean/__init__.py b/broadbean/__init__.py index f402a4ff..26d775d2 100644 --- a/broadbean/__init__.py +++ b/broadbean/__init__.py @@ -1,10 +1,10 @@ # flake8: noqa (ignore unused imports) # Version 1.0 - from . import ripasso +from ._version import __version__ +from .blueprint import BluePrint +from .broadbean import PulseAtoms from .element import Element from .sequence import Sequence -from .blueprint import BluePrint from .tools import makeVaryingSequence, repeatAndVarySequence -from .broadbean import PulseAtoms diff --git a/broadbean/blueprint.py b/broadbean/blueprint.py index 87a9e07c..6416bb49 100644 --- a/broadbean/blueprint.py +++ b/broadbean/blueprint.py @@ -160,9 +160,9 @@ def _make_names_unique(lst): for ii, ind in enumerate(inds): # Do not append numbers to the first occurence if ii == 0: - lst[ind] = '{}'.format(un) + lst[ind] = f"{un}" else: - lst[ind] = '{}{}'.format(un, ii+1) + lst[ind] = f"{un}{ii+1}" return lst @@ -240,7 +240,7 @@ def description(self): no_segs = len(self._namelist) for sn in range(no_segs): - segkey = 'segment_{:02d}'.format(sn+1) + segkey = f"segment_{sn+1:02d}" desc[segkey] = {} desc[segkey]['name'] = self._namelist[sn] if self._funlist[sn] == 'waituntil': @@ -284,9 +284,11 @@ def blueprint_from_description(cls, blue_dict): blue_dict: a dict in the same form as returned by BluePrint.description """ - knowfunctions = dict([('function PulseAtoms.{}'.format(fun), - getattr(PulseAtoms, fun)) for fun in - dir(PulseAtoms) if '__' not in fun]) + knowfunctions = { + f"function PulseAtoms.{fun}": getattr(PulseAtoms, fun) + for fun in dir(PulseAtoms) + if "__" not in fun + } seg_mar_list = list(blue_dict.keys()) seg_list = [s for s in seg_mar_list if 'segment' in s] bp_sum = cls() @@ -322,7 +324,7 @@ def init_from_json(cls, path_to_file: str) -> 'BluePrint': The JSON file needs to be structured as if it was writen by the function write_to_json """ - with open(path_to_file, 'r') as fp: + with open(path_to_file) as fp: data_loaded = json.load(fp) return cls.blueprint_from_description(data_loaded) @@ -353,9 +355,11 @@ def _makeWaitDurations(self): wait_time = argslist[pos][0] dur = wait_time - elapsed_time if dur < 0: - raise ValueError('Inconsistent timing. Can not wait until ' + - '{} at position {}.'.format(wait_time, pos) + - ' {} elapsed already'.format(elapsed_time)) + raise ValueError( + "Inconsistent timing. Can not wait until " + + f"{wait_time} at position {pos}." + + f" {elapsed_time} elapsed already" + ) else: durations[pos] = dur @@ -441,10 +445,12 @@ def changeArg(self, name, arg, value, replaceeverywhere=False): # Each function has two 'secret' arguments, SR and dur user_params = len(sig.parameters)-2 if isinstance(arg, int) and (arg not in range(user_params)): - raise ValueError('No argument {} '.format(arg) + - 'of function {}.'.format(function.__name__) + - ' Has {} '.format(user_params) + - 'arguments.') + raise ValueError( + f"No argument {arg} " + + f"of function {function.__name__}." + + f" Has {user_params} " + + "arguments." + ) # allow the user to input single values instead of (val,) no_of_args = len(self._argslist[position]) @@ -669,7 +675,7 @@ def removeSegment(self, name): try: position = self._namelist.index(name) except ValueError: - raise KeyError('No segment called {} in blueprint.'.format(name)) + raise KeyError(f"No segment called {name} in blueprint.") del self._funlist[position] del self._argslist[position] @@ -799,9 +805,11 @@ def _subelementBuilder(blueprint: BluePrint, SR: int, wait_time = argslist[pos][0] dur = wait_time - elapsed_time if dur < 0: - raise ValueError('Inconsistent timing. Can not wait until ' + - '{} at position {}.'.format(wait_time, pos) + - ' {} elapsed already'.format(elapsed_time)) + raise ValueError( + "Inconsistent timing. Can not wait until " + + f"{wait_time} at position {pos}." + + f" {elapsed_time} elapsed already" + ) else: durations[pos] = dur diff --git a/broadbean/broadbean.py b/broadbean/broadbean.py index c7ed4d75..65780345 100644 --- a/broadbean/broadbean.py +++ b/broadbean/broadbean.py @@ -53,10 +53,10 @@ def gaussian(ampl, sigma, mu, offset, SR, npts): """ dur = npts/SR time = np.linspace(0, dur, int(npts), endpoint=False) - centre = dur/2 - baregauss = np.exp((-(time-mu-centre)**2/(2*sigma**2))) - return ampl*baregauss+offset - + centre = dur / 2 + baregauss = np.exp(-((time - mu - centre) ** 2) / (2 * sigma**2)) + return ampl * baregauss + offset + @staticmethod def gaussian_smooth_cutoff(ampl, sigma, mu, offset, SR, npts): """ @@ -64,14 +64,16 @@ def gaussian_smooth_cutoff(ampl, sigma, mu, offset, SR, npts): Is by default centred in the middle of the interval - smooth cutoff by making offsetting the Gaussian so endpoint = 0 and normalizing the hight to 1 + smooth cutoff by making offsetting the Gaussian so endpoint = 0 and normalizing the hight to 1 """ dur = npts/SR time = np.linspace(0, dur, int(npts), endpoint=False) - centre = dur/2 - baregauss = np.exp((-(time-mu-centre)**2/(2*sigma**2)))-np.exp((-(0-mu-centre)**2/(2*sigma**2))) - normalization = 1/(1.0-np.exp((-(0-mu-centre)**2/(2*sigma**2)))) - return ampl*baregauss/normalization+offset + centre = dur / 2 + baregauss = np.exp(-((time - mu - centre) ** 2) / (2 * sigma**2)) - np.exp( + -((0 - mu - centre) ** 2) / (2 * sigma**2) + ) + normalization = 1 / (1.0 - np.exp(-((0 - mu - centre) ** 2) / (2 * sigma**2))) + return ampl * baregauss / normalization + offset def marked_for_deletion(replaced_by: Union[str, None]=None) -> Callable: @@ -155,7 +157,7 @@ def __getitem__(self, key): return output else: - raise KeyError('{} is not a valid key.'.format(key)) + raise KeyError(f"{key} is not a valid key.") if isinstance(key, slice): start = key.start diff --git a/broadbean/element.py b/broadbean/element.py index e5a302c8..144b7a46 100644 --- a/broadbean/element.py +++ b/broadbean/element.py @@ -304,9 +304,9 @@ def init_from_json(cls, path_to_file: str) -> 'Element': The JSON file needs to be structured as if it was writen by the function write_to_json """ - with open(path_to_file, 'r') as fp: + with open(path_to_file) as fp: data_loaded = json.load(fp) - return cls.element_from_description(data_loaded) + return cls.element_from_description(data_loaded) def changeArg(self, channel: Union[str, int], name: str, arg: Union[str, int], value: Union[int, float], @@ -335,8 +335,8 @@ def changeArg(self, channel: Union[str, int], if channel not in self.channels: raise ValueError(f'Nothing assigned to channel {channel}') - if 'blueprint' not in self._data[channel].keys(): - raise ValueError('No blueprint on channel {}.'.format(channel)) + if "blueprint" not in self._data[channel].keys(): + raise ValueError(f"No blueprint on channel {channel}.") bp = self._data[channel]['blueprint'] @@ -363,8 +363,8 @@ def changeDuration(self, channel: Union[str, int], name: str, if channel not in self.channels: raise ValueError(f'Nothing assigned to channel {channel}') - if 'blueprint' not in self._data[channel].keys(): - raise ValueError('No blueprint on channel {}.'.format(channel)) + if "blueprint" not in self._data[channel].keys(): + raise ValueError(f"No blueprint on channel {channel}.") bp = self._data[channel]['blueprint'] @@ -386,7 +386,7 @@ def _applyDelays(self, delays: List[float]) -> None: raise ValueError('Incorrect number of delays specified.' ' Must match the number of channels.') - if not sum([d >= 0 for d in delays]) == len(delays): + if not sum(d >= 0 for d in delays) == len(delays): raise ValueError('Negative delays not allowed.') # The strategy is: diff --git a/broadbean/plotting.py b/broadbean/plotting.py index fe36320d..f6442f3a 100644 --- a/broadbean/plotting.py +++ b/broadbean/plotting.py @@ -286,8 +286,8 @@ def update_minmax(chanminmax, wfmdata, chanind): # labels if pos == 0: - ax.set_ylabel('({})'.format(voltageunit)) - if pos == seqlen - 1 and not(isinstance(obj_to_plot, BluePrint)): + ax.set_ylabel(f"({voltageunit})") + if pos == seqlen - 1 and not (isinstance(obj_to_plot, BluePrint)): newax = ax.twinx() newax.set_yticks([]) if isinstance(chan, int): @@ -299,7 +299,7 @@ def update_minmax(chanminmax, wfmdata, chanind): if seq[pos+1]['type'] == 'subsequence': ax.set_xlabel('Time N/A') else: - ax.set_xlabel('({})'.format(timeunit)) + ax.set_xlabel(f"({timeunit})") # remove excess space from the plot if not chanind+1 == len(chans): diff --git a/broadbean/sequence.py b/broadbean/sequence.py index 7ea09c10..c239f318 100644 --- a/broadbean/sequence.py +++ b/broadbean/sequence.py @@ -117,16 +117,15 @@ def __add__(self, other): newseq = Sequence() N = len(self._data) - newdata1 = dict([(key, self.element(key).copy()) - for key in self._data.keys()]) - newdata2 = dict([(key+N, other.element(key).copy()) - for key in other._data.keys()]) + newdata1 = {key: self.element(key).copy() for key in self._data.keys()} + newdata2 = {key + N: other.element(key).copy() for key in other._data.keys()} newdata1.update(newdata2) newseq._data = newdata1 - newsequencing1 = dict([(key, self._sequencing[key].copy()) - for key in self._sequencing.keys()]) + newsequencing1 = { + key: self._sequencing[key].copy() for key in self._sequencing.keys() + } newsequencing2 = dict() for key, item in other._sequencing.items(): @@ -268,9 +267,9 @@ def setChannelVoltageRange(self, channel, ampl, offset): ' Use setChannelAmplitude and SetChannelOffset ' 'instead.') - keystr = 'channel{}_amplitude'.format(channel) + keystr = f"channel{channel}_amplitude" self._awgspecs[keystr] = ampl - keystr = 'channel{}_offset'.format(channel) + keystr = f"channel{channel}_offset" self._awgspecs[keystr] = offset def setChannelAmplitude(self, channel: Union[int, str], @@ -283,7 +282,7 @@ def setChannelAmplitude(self, channel: Union[int, str], channel: The channel number ampl: The channel peak-to-peak amplitude (V) """ - keystr = 'channel{}_amplitude'.format(channel) + keystr = f"channel{channel}_amplitude" self._awgspecs[keystr] = ampl def setChannelOffset(self, channel: Union[int, str], @@ -296,7 +295,7 @@ def setChannelOffset(self, channel: Union[int, str], channel: The channel number/name offset: The channel offset (V) """ - keystr = 'channel{}_offset'.format(channel) + keystr = f"channel{channel}_offset" self._awgspecs[keystr] = offset def setChannelDelay(self, channel: Union[int, str], @@ -316,7 +315,7 @@ def setChannelDelay(self, channel: Union[int, str], given. """ - self._awgspecs['channel{}_delay'.format(channel)] = delay + self._awgspecs[f"channel{channel}_delay"] = delay def setChannelFilterCompensation(self, channel: Union[str, int], kind: str, order: int=1, @@ -356,9 +355,13 @@ def setChannelFilterCompensation(self, channel: Union[str, int], ' constant and a cut-off ' 'frequency.') - keystr = 'channel{}_filtercompensation'.format(channel) - self._awgspecs[keystr] = {'kind': kind, 'order': order, 'f_cut': f_cut, - 'tau': tau} + keystr = f"channel{channel}_filtercompensation" + self._awgspecs[keystr] = { + "kind": kind, + "order": order, + "f_cut": f_cut, + "tau": tau, + } def addElement(self, position: int, element: Element) -> None: """ @@ -479,7 +482,7 @@ def description(self): """ desc = {} - for pos, elem in self._data.items(): + for pos, elem in self._data.items(): desc[str(pos)] = {} desc[str(pos)]['channels'] = elem.description try: @@ -499,7 +502,7 @@ def write_to_json(self, path_to_file: str) -> None: """ Writes sequences to JSON file - Args: + Args: path_to_file: the path to the file to write to ex: path_to_file/sequense.json """ @@ -515,7 +518,7 @@ def sequence_from_description(cls, seq_dict: dict) -> 'Sequence': seq_dict: a dict in the same form as returned by Sequence.description """ - + awgspecs = seq_dict['awgspecs'] SR = awgspecs['SR'] elem_list = list(seq_dict.keys()) @@ -528,9 +531,11 @@ def sequence_from_description(cls, seq_dict: dict) -> 'Sequence': bp_sum = BluePrint.blueprint_from_description(seq_dict[ele]['channels'][chan]) bp_sum.setSR(SR) elem.addBluePrint(int(chan), bp_sum) - ChannelAmplitude = awgspecs['channel{}_amplitude'.format(chan)] - new_instance.setChannelAmplitude(int(chan), ChannelAmplitude) # Call signature: channel, amplitude (peak-to-peak) - ChannelOffset = awgspecs['channel{}_offset'.format(chan)] + ChannelAmplitude = awgspecs[f"channel{chan}_amplitude"] + new_instance.setChannelAmplitude( + int(chan), ChannelAmplitude + ) # Call signature: channel, amplitude (peak-to-peak) + ChannelOffset = awgspecs[f"channel{chan}_offset"] new_instance.setChannelOffset(int(chan), ChannelOffset) new_instance.addElement(int(ele), elem) @@ -557,14 +562,12 @@ def init_from_json(cls, path_to_file: str) -> 'Sequence': by the function write_to_json """ new_instance = cls() - with open(path_to_file, 'r') as fp: + with open(path_to_file) as fp: data_loaded = json.load(fp) new_instance = Sequence.sequence_from_description(data_loaded) return new_instance - - @property def name(self): return self._name @@ -811,7 +814,7 @@ def _prepareForOutputting(self) -> List[Dict[int, np.ndarray]]: # Verify physical amplitude specifiations for chan in channels: - ampkey = 'channel{}_amplitude'.format(chan) + ampkey = f"channel{chan}_amplitude" if ampkey not in self._awgspecs.keys(): raise KeyError('No amplitude specified for channel ' '{}. Can not continue.'.format(chan)) @@ -820,7 +823,7 @@ def _prepareForOutputting(self) -> List[Dict[int, np.ndarray]]: delays = [] for chan in channels: try: - delays.append(self._awgspecs['channel{}_delay'.format(chan)]) + delays.append(self._awgspecs[f"channel{chan}_delay"]) except KeyError: delays.append(0) maxdelay = max(delays) @@ -864,7 +867,7 @@ def _prepareForOutputting(self) -> List[Dict[int, np.ndarray]]: # Now that the numerical arrays exist, we can apply filter compensation for chan in channels: - keystr = 'channel{}_filtercompensation'.format(chan) + keystr = f"channel{chan}_filtercompensation" if keystr in self._awgspecs.keys(): kind = self._awgspecs[keystr]['kind'] order = self._awgspecs[keystr]['order'] @@ -912,7 +915,7 @@ def outputForSEQXFile(self) -> Tuple[List[int], List[int], List[int], channels = self.element(1).channels for chan in channels: - offkey = 'channel{}_offset'.format(chan) + offkey = f"channel{chan}_offset" if offkey in self._awgspecs.keys(): log.warning("Found a specified offset for channel " "{}, but .seqx files can't contain offset " @@ -926,7 +929,7 @@ def outputForSEQXFile(self) -> Tuple[List[int], List[int], List[int], amplitudes = [] for chan in channels: - ampl = self._awgspecs['channel{}_amplitude'.format(chan)] + ampl = self._awgspecs[f"channel{chan}_amplitude"] amplitudes.append(ampl) if len(amplitudes) == 1: amplitudes.append(0) @@ -934,8 +937,8 @@ def outputForSEQXFile(self) -> Tuple[List[int], List[int], List[int], for pos in range(1, seqlen+1): element = elements[pos-1] for chan in channels: - ampl = self._awgspecs['channel{}_amplitude'.format(chan)] - wfm = element[chan]['wfm'] + ampl = self._awgspecs[f"channel{chan}_amplitude"] + wfm = element[chan]["wfm"] # check the waveform length if len(wfm) < 2400: raise ValueError('Waveform too short on channel ' @@ -943,18 +946,22 @@ def outputForSEQXFile(self) -> Tuple[List[int], List[int], List[int], 'The required minimum is 2400 points.' ''.format(chan, pos, len(wfm))) # check whether the waveform voltages can be realised - if wfm.max() > ampl/2: - raise ValueError('Waveform voltages exceed channel range ' - 'on channel {}'.format(chan) + - ' sequence element {}.'.format(pos) + - ' {} > {}!'.format(wfm.max(), ampl/2)) - if wfm.min() < -ampl/2: - raise ValueError('Waveform voltages exceed channel range ' - 'on channel {}'.format(chan) + - ' sequence element {}. '.format(pos) + - '{} < {}!'.format(wfm.min(), -ampl/2)) - element[chan]['wfm'] = wfm - elements[pos-1] = element + if wfm.max() > ampl / 2: + raise ValueError( + "Waveform voltages exceed channel range " + "on channel {}".format(chan) + + f" sequence element {pos}." + + f" {wfm.max()} > {ampl/2}!" + ) + if wfm.min() < -ampl / 2: + raise ValueError( + "Waveform voltages exceed channel range " + "on channel {}".format(chan) + + f" sequence element {pos}. " + + f"{wfm.min()} < {-ampl/2}!" + ) + element[chan]["wfm"] = wfm + elements[pos - 1] = element # Finally cast the lists into the shapes required by the AWG driver @@ -1039,7 +1046,7 @@ def outputForAWGFile(self): channels = self.element(1).channels for chan in channels: - offkey = 'channel{}_offset'.format(chan) + offkey = f"channel{chan}_offset" if offkey not in self._awgspecs.keys(): raise ValueError("No specified offset for channel " "{}, can not continue." @@ -1054,20 +1061,24 @@ def rescaler(val, ampl, off): for pos in range(1, seqlen+1): element = elements[pos-1] for chan in channels: - ampl = self._awgspecs['channel{}_amplitude'.format(chan)] - off = self._awgspecs['channel{}_offset'.format(chan)] - wfm = element[chan]['wfm'] + ampl = self._awgspecs[f"channel{chan}_amplitude"] + off = self._awgspecs[f"channel{chan}_offset"] + wfm = element[chan]["wfm"] # check whether the waveform voltages can be realised - if wfm.max() > ampl/2+off: - raise ValueError('Waveform voltages exceed channel range ' - 'on channel {}'.format(chan) + - ' sequence element {}.'.format(pos) + - ' {} > {}!'.format(wfm.max(), ampl/2+off)) - if wfm.min() < -ampl/2+off: - raise ValueError('Waveform voltages exceed channel range ' - 'on channel {}'.format(chan) + - ' sequence element {}. '.format(pos) + - '{} < {}!'.format(wfm.min(), -ampl/2+off)) + if wfm.max() > ampl / 2 + off: + raise ValueError( + "Waveform voltages exceed channel range " + "on channel {}".format(chan) + + f" sequence element {pos}." + + f" {wfm.max()} > {ampl/2+off}!" + ) + if wfm.min() < -ampl / 2 + off: + raise ValueError( + "Waveform voltages exceed channel range " + "on channel {}".format(chan) + + f" sequence element {pos}. " + + f"{wfm.min()} < {-ampl/2+off}!" + ) wfm = rescaler(wfm, ampl, off) element[chan]['wfm'] = wfm elements[pos-1] = element diff --git a/docs/Makefile b/docs/Makefile index 247efa39..d5fd0e10 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,50 +1,36 @@ -# Minimal makefile for Sphinx documentation -# Carefully tested on 18 May 2017 +# Makefile for Sphinx documentation +# -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = broadbean -SOURCEDIR = source -BUILDDIR = build -GH_PAGES_SOURCES = docs broadbean +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -.PHONY: help Makefile +.PHONY: help Makefile clean genapi htmlfast +clean: + rm -rf $(BUILDDIR)/* + rm -rf _auto + rm -rf api/generated + @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -.PHONY: html -html: - $(SPHINXBUILD) -b html $(SPHINXOPTS) . $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." +# generate api docs for instruments automatically +genapi: + sphinx-apidoc -o _auto -d 10 ../broadbean + mkdir -p api/generated/ + cp _auto/broadbean.* api/generated/ +# faster build by skipping execution of all notebooks +htmlfast: genapi + @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -D nbsphinx_execute=never -gh-pages: - git config --global user.email "bot@travics.com" - git config --global user.name "Documentation Bot" - git remote add upstream https://${GITHUB_API_KEY}@github.com/QCoDeS/broadbean.git - git fetch upstream - git checkout gh-pages - rm -rf ./* - git checkout master $(GH_PAGES_SOURCES) - git reset HEAD - cd docs && make html - mv -fv docs/$(BUILDDIR)/html/* ../ - rm -rf $(GH_PAGES_SOURCES) build - git add -A - git commit -m "Generated gh-pages for `git log master -1 --pretty=short \ - --abbrev-commit`" && git push https://${GITHUB_API_KEY}@github.com/QCoDeS/broadbean.git gh-pages ; git checkout master - - -.PHONY: execute -execute: - echo "Running the notebooks..." - jupyter nbconvert --to notebook --execute "Pulse Building Tutorial.ipynb" - jupyter nbconvert --to notebook --execute "Filter compensation.ipynb" - jupyter nbconvert --to notebook --execute "Subsequences.ipynb" - echo "Cleaning up the generated output..." - rm *nbconvert.ipynb +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile genapi + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/readme b/docs/_static/readme new file mode 100644 index 00000000..a696e6bb --- /dev/null +++ b/docs/_static/readme @@ -0,0 +1 @@ +This folder can hold images and other static files for the docs. \ No newline at end of file diff --git a/docs/changes/0.10.0.rst b/docs/changes/0.10.0.rst index e3898db5..b6791eaa 100644 --- a/docs/changes/0.10.0.rst +++ b/docs/changes/0.10.0.rst @@ -1,5 +1,5 @@ Changelog for broadbean 0.10.0 -=========================== +============================== The January 2021 release of broadbean. @@ -13,7 +13,6 @@ There are no breaking changes in this release of broadbean New: ____ -- Support for reading and writing broadbean Sequences, elements and bluprints to and from a json file +- Support for reading and writing broadbean Sequences, elements and bluprints to and from a json file - made broadbean compatiple with numpy version 1.18 - Include LICENSE in package - diff --git a/docs/changes/index.rst b/docs/changes/index.rst index 91a96fd6..9ba210a3 100644 --- a/docs/changes/index.rst +++ b/docs/changes/index.rst @@ -3,4 +3,3 @@ Changelogs .. toctree:: 0.10.0 <0.10.0> - diff --git a/docs/source/conf.py b/docs/conf.py similarity index 66% rename from docs/source/conf.py rename to docs/conf.py index 8f3b6165..d7a98617 100644 --- a/docs/source/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # broadbean documentation build configuration file, created by # sphinx-quickstart on Wed May 17 09:11:27 2017. @@ -17,10 +16,12 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) +import os +import sphinx_rtd_theme +from packaging.version import parse + +import broadbean # -- General configuration ------------------------------------------------ @@ -31,57 +32,68 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode'] +extensions = [ + "nbsphinx", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.napoleon", + "sphinx-jsonschema", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", +] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_sphinx_templates'] +templates_path = ["_sphinx_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'broadbean' -copyright = '2017, William H.P. Nielsen' -author = 'William H.P. Nielsen' +project = "broadbean" +copyright = "2022, Microsoft Quantum" +author = "Microsoft Quantum" -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.9' -# The full version, including alpha/beta/rc tags. -release = '0.9.1' +# -- Get version information ---------------------------- + +version = broadbean.__version__ +release = parse(broadbean.__version__).public # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = [] +exclude_patterns = [ + "_build", + "Thumbs.db", + ".DS_Store", + "_templates", + "_auto", + "**.ipynb_checkpoints", +] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True -# Extensions, here we enable napoleon to have Google style docstrings -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx.ext.todo'] - # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -95,18 +107,18 @@ # # html_theme_options = {} html_theme = "sphinx_rtd_theme" -html_theme_path = ["_sphinx_themes"] +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_sphinx_static'] +html_static_path = ["_static"] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'broadbeandoc' +htmlhelp_basename = "broadbeandoc" # -- Options for LaTeX output --------------------------------------------- @@ -115,15 +127,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -133,8 +142,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'broadbean.tex', 'broadbean Documentation', - 'William H.P. Nielsen', 'manual'), + ( + master_doc, + "broadbean.tex", + "broadbean Documentation", + "Microsoft Quantum", + "manual", + ), ] @@ -142,10 +156,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'broadbean', 'broadbean Documentation', - [author], 1) -] +man_pages = [(master_doc, "broadbean", "broadbean Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -154,9 +165,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'broadbean', 'broadbean Documentation', - author, 'broadbean', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "broadbean", + "broadbean Documentation", + author, + "broadbean", + "One line description of project.", + "Miscellaneous", + ), ] @@ -178,4 +195,30 @@ # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# we are using non local images for badges. These will change so we dont +# want to store them locally. +suppress_warnings = ["image.nonlocal_uri"] + +nitpicky = False + + +numfig = True + +# Use this kernel instead of the one stored in the notebook metadata: +nbsphinx_kernel_name = "python3" +# always execute notebooks. +nbsphinx_execute = "always" + +nbsphinx_allow_errors = True + +apidoc_module_dir = os.path.dirname(broadbean.__file__) +apidoc_output_dir = "reference" +apidoc_separate_modules = True +apidoc_toc_file = False +apidoc_module_first = True +apidoc_extra_args = ["--force"] diff --git a/docs/Example_Write_Read_JSON.ipynb b/docs/examples/Example_Write_Read_JSON.ipynb similarity index 100% rename from docs/Example_Write_Read_JSON.ipynb rename to docs/examples/Example_Write_Read_JSON.ipynb diff --git a/docs/Filter compensation.ipynb b/docs/examples/Filter_compensation.ipynb similarity index 100% rename from docs/Filter compensation.ipynb rename to docs/examples/Filter_compensation.ipynb diff --git a/docs/Making output for Tektronix AWG70000A.ipynb b/docs/examples/Making_output_for_Tektronix_AWG70000A.ipynb similarity index 100% rename from docs/Making output for Tektronix AWG70000A.ipynb rename to docs/examples/Making_output_for_Tektronix_AWG70000A.ipynb diff --git a/docs/Pulse Building Tutorial.ipynb b/docs/examples/Pulse_Building_Tutorial.ipynb similarity index 100% rename from docs/Pulse Building Tutorial.ipynb rename to docs/examples/Pulse_Building_Tutorial.ipynb diff --git a/docs/Subsequences.ipynb b/docs/examples/Subsequences.ipynb similarity index 100% rename from docs/Subsequences.ipynb rename to docs/examples/Subsequences.ipynb diff --git a/docs/examples/index.rst b/docs/examples/index.rst new file mode 100644 index 00000000..691f64a9 --- /dev/null +++ b/docs/examples/index.rst @@ -0,0 +1,12 @@ +Broadbean Examples +================== + +.. toctree:: + :glob: + :maxdepth: 1 + + Pulse_Building_Tutorial.ipynb + Making_output_for_Tektronix_AWG70000A.ipynb + Example_Write_Read_JSON.ipynb + Filter_compensation.ipynb + Subsequences.ipynb diff --git a/docs/execute_notebooks.cmd b/docs/execute_notebooks.cmd deleted file mode 100644 index f5755eb4..00000000 --- a/docs/execute_notebooks.cmd +++ /dev/null @@ -1,6 +0,0 @@ -echo "Running the notebooks..." -jupyter nbconvert --to notebook --execute "Pulse Building Tutorial.ipynb" -jupyter nbconvert --to notebook --execute "Filter compensation.ipynb" -jupyter nbconvert --to notebook --execute "Subsequences.ipynb" -echo "Cleaning up the generated output..." -rm *nbconvert.ipynb diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..88d1fa64 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,39 @@ +.. broadbean documentation master file, created by + sphinx-quickstart on Wed May 17 09:11:27 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to broadbean's documentation! + +The broadbean package is a tool for creating and manipulating pulse +sequences.The broadbean module lets the user compose and manipulate pulse sequences. +The aim of the module is to reduce pulse building to the logical minimum of +specifications so that building and manipulation become as easy as saying "Gimme +a square wave, then a ramp, then a sine, and then wait for 10 ms" and, in particular, +"Do the same thing again, but now with the sine having twice the frequency it had before". + +The little extra module called ripasso performs frequency filtering and frequency filter +compensation. It could be useful in a general setting and is therefore factored out +to its own module. + +The name: The broad bean is one of my favourite pulses. + + +Documentation +------------- + +.. toctree:: + :maxdepth: 2 + + api/generated/broadbean + start/index + examples/index + changes/index + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..ed0399fc --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,64 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help +if "%1" == "clean" goto clean + + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +REM generate api docs +REM This is not part of any job since it should be done +REM for all jobs except clean and help. +REM Note that all folders after the first one are excluded +REM (see sphinx-apidoc help for more info). +REM Also note that exclusion of "keysight" (lower-case name) +REM is due to duplication of the folder in git that happened +REM a long time ago (i.e. "Keysight", the upper-case, is used +REM for storing drivers, not the lower-case one). +sphinx-apidoc -o _auto -d 10 ..\broadbean +mkdir api\generated\ +copy _auto\broadbean.* api\generated\ + +if "%1" == "htmlfast" goto htmlfast + +REM default build used if no other brach is used +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:htmlfast +%SPHINXBUILD% -M html %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -D nbsphinx_execute=never +goto end + +:clean +del /q /s "_auto" +del /q /s "api\generated" +%SPHINXBUILD% -M clean %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:end +popd diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index cc165326..00000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. broadbean documentation master file, created by - sphinx-quickstart on Wed May 17 09:11:27 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to broadbean's documentation! - -The broadbean package is a tool for creating and manipulating pulse -sequences. - -===================================== - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - -.. automodule:: broadbean.broadbean - :members: - -.. automodule:: broadbean.ripasso - :members: - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/start/index.rst b/docs/start/index.rst new file mode 100644 index 00000000..269b1446 --- /dev/null +++ b/docs/start/index.rst @@ -0,0 +1,141 @@ +.. _gettingstarted: + +Getting Started +=============== + +.. toctree:: + :maxdepth: 2 + + +Requirements +------------ + +You need a working ``python 3.7`` installation, as the minimum Python version (at present, +we recommend ``python 3.10``), to be able to use ``broadbean``. We highly recommend installing +Miniconda, which takes care of installing Python and managing packages. In the following +it will be assumed that you use Miniconda. Download and install it from `here `_. +Make sure to download the latest version with ``python 3.10``. + +Once you download, install Miniconda according to the instructions on screen, +choosing the single user installation option. + +The next section will guide you through the installation of broadbean on Windows. + + +Installation +------------ +Before you install broadbean you have to decide whether you want to install the +latest stable published release or if you want to get the latest developer version +from broadbean repository on Azure Devops. To install the official package you will need to +configure your computer + +Create a broadbean environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As mentioned above, it is recommended to use ``broadbean`` from a ``conda`` environment. +We create that by executing the following command: + +.. code:: bash + + conda create -n broadbean-env python=3.10 + +This will create a python 3.9 environment named broadbean-env. +Once the environment is created close your shell and open it again to ensure that changes take effect. + + +Installing the latest broadbean release +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``broadbean`` package can be installed using pip: + +.. code:: bash + + conda activate broadbean-env + pip install broadbean + + +Setting up your development environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default ``broadbean`` installation does not include packages such as ``pytest`` that are required for testing and development. +For development and testing, install ``broadbean`` with the ``test`` feature. +Note that ``broadbean`` requires ``python v3.7``, so be sure to include that option when creating your new development environment. +If you run ``broadbean`` from Jupyter notebooks, you may also need to install ``jupyter`` into the development environment. + +.. code:: bash + + conda create -n broadbean-development python=3.10 + conda activate broadbean-development + cd + pip install -e .[test] + +Updating Broadbean +~~~~~~~~~~~~~~~~~~ + +If you have installed ``broadbean`` with ``pip``, run the following to update: + +.. code:: bash + + pip install --upgrade broadbean + +Updates to ``broadbean`` are quite frequent, so please check regularly. + +If you have installed ``broadbean`` from the cloned ``git`` repository, pull the ``broadbean`` +repository using your favorite method (git bash, git shell, github desktop, ...). + +Keeping your environment up to date +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Dependencies are periodically been adjusted for ``broadbean`` (and for ``qcodes``, which ``broadbean`` is built upon) +and new versions of packages that ``broadbean`` depends on get released. +Conda/Miniconda itself is also being updated. + +Hence, to keep the broadbean conda environment up to date, please run for the activated broadbean environment: + +.. code:: bash + + conda update -n base conda -c defaults + conda env update + +The first line ensures that the ``conda`` package manager it self is +up to date, and the second line will ensure that the latest versions of the +packages used by ``broadbean`` are installed. See +`here `__ for more +documentation on ``conda env update``. + +If you are using broadbean from an editable install, you should also reinstall ``broadbean`` +before upgrading the environment to make sure that dependencies are tracked correctly using: + +.. code:: bash + + pip uninstall broadbean + pip install -e + +Note that if you install packages yourself into the same +environment it is preferable to install them using ``conda``. There is a chance that +mixing packages from ``conda`` and ``pip`` will produce a broken environment, +especially if the same package is installed using both ``pip`` and ``conda``. + + +Using broadbean +--------------- +For using broadbean, as with any other python library, it is useful to use an +application that facilitates the editing and execution of python files. Some +options are: + + - **Jupyter**, a browser based notebook + - **Vscode**, IDE with ipython capabilities + - **Spyder**, an integrated development environment + +For other options you can launch a terminal either via the *Anaconda Navigator* +by selecting *broadbean* in the *Environments tab* and left-clicking on the *play* +button or by entering + +.. code:: bash + + activate broadbean + +in the *Anaconda prompt*. + +From the terminal you can then start any other application, such as *IPython* or +just plain old *Python*. diff --git a/pyproject.toml b/pyproject.toml index 1bba5b36..36a1c2df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,3 +33,9 @@ match = ["v*"] [tool.versioningit.onbuild] source-file = "broadbean/_version.py" build-file = "broadbean/_version.py" + +[tool.darker] +isort = true + +[tool.isort] +profile = "black" diff --git a/requirements.txt b/requirements.txt index 0a4a65f6..f9149841 100644 --- a/requirements.txt +++ b/requirements.txt @@ -56,6 +56,7 @@ numpy~=1.23.0; python_version>='3.8' mypy~=0.961 mypy-extensions~=0.4.3 nbclient~=0.6.4 +nbsphinx~=0.8.9 nbconvert~=6.5.0 nbformat~=5.4.0 nest-asyncio~=1.5.5 @@ -86,6 +87,7 @@ pywin32==304; sys_platform == 'win32' pywinpty==2.0.5; sys_platform == 'win32' PyYAML~=6.0 pyzmq~=23.2.0 +qcodes~=0.34.1 qtconsole~=5.3.1 QtPy~=2.1.0 requests~=2.28.0 @@ -103,6 +105,8 @@ sphinxcontrib-htmlhelp~=2.0.0 sphinxcontrib-jsmath~=1.0.1 sphinxcontrib-qthelp~=1.0.3 sphinxcontrib-serializinghtml~=1.1.5 +sphinx-jsonschema~=1.19.1 +sphinxcontrib-apidoc~=0.3.0 stack-data~=0.3.0 terminado~=0.15.0 tinycss2~=1.1.1 diff --git a/setup.cfg b/setup.cfg index 6fdfc48c..d31940a6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,9 +2,9 @@ name = broadbean maintainer = QCoDeS Core Developers maintainer_email = qcodes-support@microsoft.com -description = Package for easily generating and manipulating signal - pulses. Developed for use with qubits in the quantum - computing labs of Copenhagen, Delft, and Sydney, but +description = Package for easily generating and manipulating signal + pulses. Developed for use with qubits in the quantum + computing labs of Copenhagen, Delft, and Sydney, but should be generally useable. keywords = Pulsebuilding signal processing arbitrary waveforms long_description = file: README.md @@ -30,7 +30,7 @@ broadbean = include_package_data = True packages=find: python_requires = >=3.7 -install_requires = +install_requires = numpy>=1.12.1 matplotlib schema @@ -46,4 +46,3 @@ test = types-pytz>=2021.3.0 jupyter>=1.0.0 hypothesis>=5.49.0 -