diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..3449de1 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= python -m sphinx +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 + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..42d12d7 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,36 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html +import sys,os +#sys.path.insert(0, os.path.abspath('../src')) + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'DAMARIS' +copyright = '2026, Markus Rosenstihl' +author = 'Markus Rosenstihl' + +version = '0.19' +release = '0.19' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + ] + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +language = 'en' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +#html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..7a58957 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,38 @@ +.. DAMARIS documentation master file, created by + sphinx-quickstart on Mon Mar 23 15:55:16 2026. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +DAMARIS documentation +===================== + +Add your content using ``reStructuredText`` syntax. See the +`reStructuredText `_ +documentation for details. + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +API Reference +------------- + +Experiments +~~~~~~~~~~~ + +.. automodule:: damaris.experiments.Experiment + :members: + :undoc-members: + +Data Objects +~~~~~~~~~~~~ + +.. automodule:: damaris.data.Accumulation + :members: + :undoc-members: + +.. automodule:: damaris.data.ADC_Result + :members: + :undoc-members: + diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..32bb245 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%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.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/pyproject.toml b/pyproject.toml index 81c8075..f9c1dab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,6 @@ maintainers = [ [project.scripts] DAMARIS3 = "damaris.__main__:main" - [tool.setuptools] packages = [ "damaris", diff --git a/src/experiments/Experiment.py b/src/experiments/Experiment.py index 6ce7916..eed19e5 100644 --- a/src/experiments/Experiment.py +++ b/src/experiments/Experiment.py @@ -71,10 +71,13 @@ from . import dac class Experiment: """ Class holding the complete state tree for a single experiment. This state tree represents one - program on the pulse card. It is written in a single file and picked up by the backend. - """ - ## Experiment class holding the state tree + program on the pulse card. It is written in a single file and picked up by the backend. + :param Gating gating: gate length in s + :param list rf_sources: list of rf sources ttl channels + :param list rf_gates: list of rf gates ttl channels len(rf_gates) == len(rf_sources) + + """ job_id = 0 def __init__(self, gating=None, rf_sources=[], rf_gates=[]): @@ -102,14 +105,6 @@ class Experiment: # Commands ------------------------------------------------------------------------------------- - ## Creates a state with ttl signals of duration *length*. - # - # **Example:** - # ttl_pulse(length=1e-6,value=3) - # will create a ttl pulse on channels 0 and 1 (2**0 + 2**1) of duration 1us - # @param length time length if this state - # @param channel select a single channel (1...24) - # @param value select the channels via decimal representation (2**0 + 2**1 ...) def ttl_pulse(self, length, channel = None, value = None): """ Creates a state with length **length** and switches @@ -117,8 +112,7 @@ class Experiment: :param float length: pulse length in seconds :param int channel: selects a single channel (No. 1 - 24) - :param int value: lines to set (integer) for example value=3 selects channels 1 and 2 (2**1 + 2**2) - + :param int value: lines to set (integer) for example value=3 selects channels 0 and 1 (2**0 + 2**1) """ the_value=0 if value is not None: @@ -150,12 +144,11 @@ class Experiment: def rf_pulse(self, length=None, phase=0, source=0): """ - make an rf pulse, including gating and phase switching + Make an rf pulse, including gating and phase switching. Only possible if Experiment is configured. :param float length: pulse length :param float phase: pulse phase :param int source: source id - :return: """ if not self.gating: raise SyntaxError("Can not use rf_pulse without configuration: Experiment(gating=None, rf_sources=[], rf_gates=[]") @@ -167,7 +160,9 @@ class Experiment: def state_start(self, time): """ Starts a state in the pulse programs with duration *time*. - This must be closed with state_end() + This must be closed with :func:`state_end`. + + Typically only used in very special cases. """ self.state_list.append('\n' % repr(time)) self.total_time[-1] += time @@ -175,13 +170,10 @@ class Experiment: ## End of *state_start* def state_end(self): """ - Closes a previous start_state() + Closes a previous :func:`state_start` """ self.state_list.append('\n') - ## An empty state doing nothing - # @param time Duration of this state - # @param ttls Additinional ttl channels def wait(self, time, ttls=None, gating=False): """ Wait specified **time** doing nothing. @@ -201,24 +193,19 @@ class Experiment: self.total_time[-1] += time - ## Records data with given number of samples, sampling-frequency frequency and sensitivity - # @param samples Number of samples to record - # @param frequency Sampling frequency - # @param timelength Length of this state, per default calculated automatically - # @param sensitivity Sensitivity in Umax/V - # @param ttls Additional ttl channels + def record(self, samples, frequency, timelength=None, sensitivity = None, ttls=None, channels = 3, offset = None, impedance = None): """ - Records data with given number of samples, sampling frequency and sensitivity. - Optionally, the time length of this state can be specified. If not specified **timelength** is + Records data with a given number of samples, sampling frequency and sensitivity. + Optionally, the time length of this state can be specified. If not specified, **timelength** is deduced from **samples**/**frequency**: :param int samples: Number of samples to record - :param float frequency: Sampling frequency + :param float frequency: Sampling frequency / Hz :param float timelength: Length of this state, per default calculated automatically - :param float sensitivity: Sensitivity in Umax/V - :param int ttls: lines to set (integer) - :param int channels: default=3, Number of channels counting from 0 + :param float sensitivity: Sensitivity in U_MAX/V + :param int ttls: additional ttl lines to set (integer) + :param int channels: channels to activate, default=3 (0+1) """ attributes='s="%d" f="%d"'%(samples,frequency)#%g if channels != 1 and channels != 3 and channels != 5 and channels != 15: @@ -270,11 +257,11 @@ class Experiment: def loop_start(self, iterations): """ creates a loop of given number of iterations and has to be closed by loop_end(). - Commands inside the loop can not change, i.e. the parameters are the same for each loop run. + Commands inside the loop can not change, i.e., the parameters are the same for each loop run. This loop is created on the pulse programmer, thus saving commands. - One must close the loop with loop_end (see below)! + Close the loop with :func:`loop_end`. - :param int iterations: iterations to loop + :param int iterations: number of iterations to loop """ l = StateLoop(iterations) self.state_list.append(l) @@ -288,7 +275,7 @@ class Experiment: ## End loop state def loop_end(self): """ - Closes a loop_start() statement. + Closes a l:func:`loop_start` statement. """ # (This line could probably be guarded by a mutex) self.state_list = self.list_stack.pop(-1) @@ -297,11 +284,7 @@ class Experiment: loopiterations=self.loop_iterations.pop(-1) self.total_time[-1] += looptime*loopiterations - ## Set the frequency and phase of the frequency source. - ## This state needs 2us. - # @param frequency New frequency in Hz - # @param phase New phase in degrees - # @param ttls Additional ttl channels + def set_frequency(self, frequency, phase, ttls=0): """ Sets the frequency and phase of the frequency source and optionally further channels. @@ -309,7 +292,7 @@ class Experiment: :param float frequency: frequency to set in Hz :param float phase: phase to set - :param int ttls: default=0, lines to set (integer) + :param int ttls: default=0, additional ttl lines to set (integer) """ s_content = '' % (frequency, phase) if ttls != 0: @@ -318,16 +301,10 @@ class Experiment: self.total_time[-1] += 2e-6 - ## Creates a, possibly shaped, pulsed gradient. - # @param dac_value DAC value to set - # @param length Duration of the state, minimum length is 42*90ns=3.78us (default) - # @param shape Tuple of (shape, resolution/seconds), shape can be one of: rec (default), sin2, sin - # @param is_seq If set to *True*, do NOT set DAC to zero after this state - # @param trigger Additional ttl channels def set_pfg(self, dac_value=None, length=None, shape=('rec', 0), trigger=4, is_seq=False): """ - This sets the value for the PFG, it also sets it back automatically. - If you don't whish to do so (i.e. line shapes) set is_seq=1 + This sets the value for the PFG, it also sets it back to dac_value=0 automatically. + If you don't wish to do so (i.e. line shapes) set is_seq=1 If you want to set a trigger, set trigger (default=4, i.e. channel 2) If you want shaped gradients: shape=(ashape, resolution), ashape can be rec, sin2, sin @@ -392,22 +369,15 @@ class Experiment: else: # don't know what to do raise SyntaxError("form is unknown: %s"%form) - - ## sets the value of a DAC - # @param dac_value DAC value to set - # @param dac_id ID of the dac in case of multiple DAC(default=1) - # @param length Duration of the state - # @param is_seq If set to *True*, do NOT set DAC to zero after this state - # @param ttls Additional ttl channels def set_dac(self, dac_value, dac_id=1, length=None, is_seq=False, ttls=0): """ - This sets the value for the DAC and if given the TTLs. - It also sets it back to zero automatically. + This sets the value for the DAC and if given additional ttl lines. + It also sets it back to zero automatically when is_seq=False. If you don't wish to set the value back to zero (i.e. line shapes) set is_seq=True The state length is at least 3.78 µs (is_seq=1) or 7.28µs (is_seq=0). - :param int dac_value: dac value, between -2**19-1 and +2**19 + :param int dac_value: DAC value, between -2**19-1 and +2**19 :param int dac_id: default=1, which DAC to control :param float length: default=None, length of this state in seconds. If *None* length=42*90ns=3.78µs :param bool is_seq: default=False, do not reset DAC to 0 (zero) if True @@ -429,10 +399,6 @@ class Experiment: self.total_time[-1] += 42*9e-8 - ## sets the phase of the frequency source. - ## This state needs 0.5us, though the phase switching time is dependent on the frequency source - # @param phase New phase to set - # @param ttls Additional ttl channels def set_phase(self, phase, ttls=0): """ Sets the phase of the RF source to this value. @@ -450,11 +416,7 @@ class Experiment: self.total_time[-1] += 0.5e-6 - ## sets a description which is carried via the back end result - ## file to the result script in the front end. In the result script - ## you can extract the description with get_description(key) - # @param key Name of description - # @param value Value of description + def set_description(self, key, value): """Sets a description which is carried via the back end result file to the result script in the front end. In the result script @@ -467,7 +429,6 @@ class Experiment: print('Warning: Overwriting existing description "%s" = "%s" with "%s"' % (key, self.description[key], value)) self.description[key] = value - ## set the PTS310/PTS500 frequency source to local mode def set_pts_local(self): """ Sets the PTS310/PTS500 frequency source to local mode. @@ -483,7 +444,7 @@ class Experiment: # Public Methods ------------------------------------------------------------------------------- - def get_job_id(self): + def get_job_id(self) -> int: """ Returns the job-id of the current experiment @@ -554,13 +515,11 @@ class Experiment: def get_length(self): timelength = 0.0 - #calculate the correct timelength also for unclosed loops, if there is no unclosed loop #the timelength of this experiment is self.total_time[-1]. for i in range(len(self.total_time)): timelength += self.total_time[i] timelength *= self.loop_iterations[i] - return timelength class Quit(Experiment): @@ -570,8 +529,6 @@ class Quit(Experiment): # /Public Methods ------------------------------------------------------------------------------ - - def self_test(): e = Experiment() e.set_description("key", "value")