From 03ff184e64dd8f2f21295f62954488ea8c55fa29 Mon Sep 17 00:00:00 2001 From: hechtprojects Date: Tue, 22 Oct 2024 18:16:55 +0200 Subject: [PATCH] website restructured; plot.particles and plot.fields type hint corrected --- README.md | 2 +- amep/load.py | 13 +- amep/plot.py | 4 +- amep/reader.py | 470 +----------------- doc/source/conf.py | 18 +- .../datastructures/particle_data_reader.md | 20 - doc/source/{user_guide => }/howto_cite.rst | 6 +- doc/source/index.rst | 6 +- .../datastructures/field_data.rst | 0 .../datastructures/function_flow.rst | 0 .../{ => user_guide}/datastructures/index.rst | 4 - doc/source/user_guide/index.rst | 2 +- 12 files changed, 26 insertions(+), 519 deletions(-) delete mode 100644 doc/source/datastructures/particle_data_reader.md rename doc/source/{user_guide => }/howto_cite.rst (95%) rename doc/source/{ => user_guide}/datastructures/field_data.rst (100%) rename doc/source/{ => user_guide}/datastructures/function_flow.rst (100%) rename doc/source/{ => user_guide}/datastructures/index.rst (84%) diff --git a/README.md b/README.md index 852b3f2..1a9e189 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ simulation data. | Observable | Particles | Fields | -|:----------:|:---------:|:------:| +|:-----------|:---------:|:------:| | **Spatial Correlation Functions:** ||| | RDF (radial pair distribution function) | ✔ | ➖ | | PCF2d (2d pair correlation function) | ✔ | ➖ | diff --git a/amep/load.py b/amep/load.py index d155936..d552a35 100644 --- a/amep/load.py +++ b/amep/load.py @@ -33,7 +33,7 @@ import os import h5py -from .reader import LammpsReader, H5amepReader, ContinuumReader, GSDReader +from .reader import LammpsReader, H5amepReader, ContinuumReader from .trajectory import ParticleTrajectory,FieldTrajectory from .base import TRAJFILENAME, BaseEvalData, BaseDatabase, LOADMODES from .base import check_path, get_module_logger @@ -226,16 +226,7 @@ def traj( **kwargs ) return FieldTrajectory(reader) - elif mode == 'gsd': - reader = GSDReader( - directory, - savedir, - trajfile = trajfile, - deleteold = deleteold, - verbose = verbose, - **kwargs - ) - return ParticleTrajectory(reader) + # here one has to check both the amep version with which the file has been # created (reader.version) and the data type (particles or fields) - # the latter is needed to decide whether a ParticleTrajectory or a diff --git a/amep/plot.py b/amep/plot.py index ca3b7e6..8e3ba75 100644 --- a/amep/plot.py +++ b/amep/plot.py @@ -909,7 +909,7 @@ def box(axis: mpl.axes.Axes, box_boundary: np.ndarray, **kwargs) -> None: def particles( - ax: mpl.axes, coords: np.ndarray, box_boundary: np.ndarray, + ax: mpl.axes.Axes, coords: np.ndarray, box_boundary: np.ndarray, radius: np.ndarray | float, scalefactor: float = 1.0, values: np.ndarray | None = None, cmap: list | str = 'viridis', set_ax_limits: bool = True, @@ -1081,7 +1081,7 @@ def particles( def field( - ax: mpl.axes, density: np.ndarray, X: np.ndarray, Y: np.ndarray, + ax: mpl.axes.Axes, density: np.ndarray, X: np.ndarray, Y: np.ndarray, cmap: list | str = 'plasma', box_boundary: np.ndarray | None = None, vmin: float | None = None, vmax: float | None = None, cscale: str = 'lin', verbose: bool = False, **kwargs diff --git a/amep/reader.py b/amep/reader.py index 16e93c6..be74704 100644 --- a/amep/reader.py +++ b/amep/reader.py @@ -37,7 +37,6 @@ import warnings import h5py import string -from gsd import hoomd import numpy as np from tqdm.autonotebook import tqdm @@ -1095,471 +1094,4 @@ def __detect_delimiter( if delimiter == []: return None - return delimiter - - -class GSDReader(BaseReader): - '''Reads GSD files and writes the containing data to an hdf5 file. - ''' - - def __init__( - self, directory: str, savedir: str, start: float = 0.0, - stop: float = 1.0, nth: int = 1, filename: str = 'trajectory.gsd', - trajfile: str = TRAJFILENAME, deleteold: bool = False, - verbose: bool = False) -> None: - r''' - Reader for simulation data in the gsd file format (hoomd-blue). - - TODO in progress - - real-time not in gsd file - - Parameters - ---------- - directory : str - Simulation directory. - savedir : str - Directory in which the trajectory file will be stored. - start : float, optional - Start reading data from this fraction of the whole trajectory. - The default is None. - stop : float, optional - Stop reading data from this fraction of the whole trajectory. - The default is None. - nth : int, optional - Read each nth dump file. The default is None. - filename : str, optional - File name of the gsd trajectory file. The default is 'trajectory.gsd'. - trajfile : str, optional - Name of the hdf5 trajectory file that is created when an object of - this class is initialized. The default is TRAJFILENAME. - deleteold : bool, optional - If True, an existing old h5amep trajectory file # - will be removed. The default is False. - verbose : bool, optional - If True, runtime information is printed. The default is False. - - Returns - ------- - None. - - ''' - # init class logger - self.__log = get_class_logger(__name__, self.__class__.__name__) - - # back up trajectory file - if os.path.exists(os.path.join(savedir, trajfile)): - # rename trajectory to backup filename "#+" - if os.path.exists(os.path.join(savedir, "#"+trajfile)): - if verbose: - self.__log.info( - f"Existing old file #{trajfile} will be replaced." - ) - os.remove(os.path.join(savedir, "#"+trajfile)) - os.rename( - os.path.join(savedir, trajfile), - os.path.join(savedir, "#"+trajfile) - ) - if verbose: - self.__log.info( - f"Renamed {trajfile} to #{trajfile}." - ) - # super(LammpsReader, self).__init__(directory, start, stop, nth, "#temp#"+trajfile) - super().__init__(os.path.abspath(savedir), start, stop, nth, "#temp#"+trajfile) - self.directory = directory - - try: - # self.__filename = filename # important: self.filename ≠ self.__filename - - # load gsd file - gsd_file = hoomd.open(os.path.join(directory,filename)) - - first = int(self.start*len(gsd_file)) # first frame index - last = int(self.stop*len(gsd_file)) # last frame index - - # select part of gsd.hoomd.HOOMDTrajectory ( = gsd.hoomd._HOOMDTrajectoryView ) - gsd_file = gsd_file[first:last:self.nth] - - # total number of files - self.__nframes = len(gsd_file) - - # array of number of time steps - steps = np.zeros(self.__nframes, dtype=int) - - # load data from dump files - with h5py.File(os.path.join(self.savedir, "#temp#"+trajfile), 'a') as root: - # Set type of h5amep file to particle - root.attrs["type"] = "particle" - - # If no ID is specified only one warning is issued to the user. - # Trigger idwarning is set to false: - idwarning=False - - # loop through all dump files - for n, gsd_frame in enumerate(tqdm(gsd_file)): - - # get number of time steps - step = gsd_frame.configuration.step - - # get total number of atoms - N = gsd_frame.particles.N - - # create new group for the given step in the 'frames' group - if str(step) not in root['frames'].keys(): - frame = root['frames'].create_group(str(step)) - else: - frame = root['frames'][str(step)] - - # get boundaries of the simulation box - gsd_box = gsd_frame.configuration.box - if np.any(gsd_box[3:] != 0): - raise Exception(f"GSDReader: box tilt factors {gsd_box[3:]} must be 0.") - box=np.zeros((3,2)) - box[:,0]=-gsd_box[:3]/2 - box[:,1]=gsd_box[:3]/2 # hoomd-gsd box always centered around 0 - - # add box to hdf5 file - if 'box' not in frame.keys(): - frame.create_dataset('box', - (3, 2), - data=box, - dtype=DTYPE, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - else: - frame['box'][:] = box - - # get center of the simulation box - center = (box[:, 1]+box[:, 0])/2 - - # add center to hdf5 file - if 'center' not in frame.keys(): - frame.create_dataset('center', - (3,), - data=center, - dtype=DTYPE, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - else: - frame['center'][:] = center - - print(center) - continue - - # extract the data - data = np.zeros((N, nparams), dtype=DTYPE) - # ignore other lines (N+9 and larger) - # (LAMMPS sometimes adds an empty line at the end - # which would cause problems) - for l, line in enumerate(lines[9:N+9]): - data[l] = np.fromstring(line, sep=' ') - - # add data to hdf5 file - coords = np.zeros((N, 3), dtype=DTYPE) - uwcoords = np.zeros((N, 3), dtype=DTYPE) # unwrapped - velocities = np.zeros((N, 3), dtype=DTYPE) - forces = np.zeros((N, 3), dtype=DTYPE) - orientations = np.zeros((N, 3), dtype=DTYPE) - omegas = np.zeros((N, 3), dtype=DTYPE) - torque = np.zeros((N, 3), dtype=DTYPE) - angmom = np.zeros((N, 3), dtype=DTYPE) - - # sort data by id if available - if "id" in keys: - sorting = np.argsort(data[:, keys.index("id")]) - data = data[sorting, :] - else: - # Warn user if data could not be sorted. - idwarning = True - # side-note from the documentation: - # """Repetitions of a particular warning for the same - # source location are typically suppressed.""" - - unwrapped_available = False - d = 0 - for i, key in enumerate(keys): - if key == 'x': - coords[:, 0] = data[:, i] - if not np.all((data[:, i] == 0.0)): - d += 1 - elif key == 'y': - coords[:, 1] = data[:, i] - if not np.all((data[:, i] == 0.0)): - d += 1 - elif key == 'z': - coords[:, 2] = data[:, i] - if not np.all((data[:, i] == 0.0)): - d += 1 - elif key == 'xu': - uwcoords[:, 0] = data[:, i] - unwrapped_available = True - elif key == 'yu': - uwcoords[:, 1] = data[:, i] - unwrapped_available = True - elif key == 'zu': - uwcoords[:, 2] = data[:, i] - unwrapped_available = True - elif key == 'vx': - velocities[:, 0] = data[:, i] - elif key == 'vy': - velocities[:, 1] = data[:, i] - elif key == 'vz': - velocities[:, 2] = data[:, i] - elif key == 'fx': - forces[:, 0] = data[:, i] - elif key == 'fy': - forces[:, 1] = data[:, i] - elif key == 'fz': - forces[:, 2] = data[:, i] - elif key == 'mux': - orientations[:, 0] = data[:, i] - elif key == 'muy': - orientations[:, 1] = data[:, i] - elif key == 'muz': - orientations[:, 2] = data[:, i] - elif key == 'omegax': - omegas[:, 0] = data[:, i] - elif key == 'omegay': - omegas[:, 1] = data[:, i] - elif key == 'omegaz': - omegas[:, 2] = data[:, i] - elif key == 'tqx': - torque[:, 0] = data[:, i] - elif key == 'tqy': - torque[:, 1] = data[:, i] - elif key == 'tqz': - torque[:, 2] = data[:, i] - elif key == 'angmomx': - angmom[:, 0] = data[:, i] - elif key == 'angmomy': - angmom[:, 1] = data[:, i] - elif key == 'angmomz': - angmom[:, 2] = data[:, i] - elif key == 'type': - if key not in frame.keys(): - frame.create_dataset(key, - (N,), - data=data[:, i], - dtype=int, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - else: - frame[key][:] = data[:, i] - elif key == 'id': - if key not in frame.keys(): - frame.create_dataset(key, - (N,), - data=data[:, i], - dtype=int, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - else: - frame[key][:] = data[:, i] - else: - if key not in frame.keys(): - frame.create_dataset(key, - (N,), - data=data[:, i], - dtype=DTYPE, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - else: - frame[key][:] = data[:, i] - - if 'coords' not in frame.keys(): - frame.create_dataset('coords', - (N, 3), - data=coords, - dtype=DTYPE, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - else: - frame['coords'][:] = coords - - if 'uwcoords' not in frame.keys() and unwrapped_available: - frame.create_dataset('uwcoords', - (N, 3), - data=uwcoords, - dtype=DTYPE, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - elif 'uwcoords' in frame.keys() and unwrapped_available: - frame['uwcoords'][:] = uwcoords - - if 'velocities' not in frame.keys(): - frame.create_dataset('velocities', - (N, 3), - data=velocities, - dtype=DTYPE, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - else: - frame['velocities'][:] = velocities - - if 'forces' not in frame.keys(): - frame.create_dataset('forces', - (N, 3), - data=forces, - dtype=DTYPE, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - else: - frame['forces'][:] = forces - - if 'orientations' not in frame.keys(): - frame.create_dataset('orientations', - (N, 3), - data=orientations, - dtype=DTYPE, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - else: - frame['orientations'][:] = orientations - - if 'omegas' not in frame.keys(): - frame.create_dataset('omegas', - (N, 3), - data=omegas, - dtype=DTYPE, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - else: - frame['omegas'][:] = omegas - - if 'torque' not in frame.keys(): - frame.create_dataset('torque', - (N, 3), - data=torque, - dtype=DTYPE, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - else: - frame['torque'][:] = torque - - # spatial dimension - frame.attrs['d'] = d - - # add type if not specified - if 'type' not in frame.keys(): - frame.create_dataset('type', - (N,), - data=np.ones(N), - dtype=int, - compression=COMPRESSION, - shuffle=SHUFFLE, - fletcher32=FLETCHER) - - steps[n] = step - - del lines - - if idwarning: - # Warn user if data could not be sorted. - self.__log.warning( - "No IDs specified. Data may be unsorted between "\ - "individual frames." - ) - - self.steps = steps - # get time step from log file (this also sets self.times) - self.dt = self.__get_timestep_from_logfile() - self.d = d - except Exception as e: - os.remove(os.path.join(savedir, "#temp#"+trajfile)) - # print("LammpsReader: loading dump files failed.") - if "LammpsReader: no dump files in this directory." in e.args[0]: - # print(e) - # we should not raise an error in this case. - pass - # print(f"LammpsReader: error while loading dump files. {e}") - raise - else: - # if no exception. rename #temp# and delete #. - if os.path.exists(os.path.join(savedir, trajfile)) and verbose: - # This case should not occur if AMEP is used properly. - # Possible scenario: - # two instances of AMEP reading and writing in the same directory. - self.__log.info( - f"File {trajfile} already exists. "\ - "Overwriting existing file." - ) - os.rename(os.path.join(savedir, "#temp#"+trajfile), os.path.join(savedir, trajfile)) - self.filename = trajfile - - # delete old trajectory file # if specified by user. - if deleteold and os.path.exists(os.path.join(savedir, "#"+trajfile)): - os.remove(os.path.join(savedir, "#"+trajfile)) - if verbose: - self.__log.info( - f"Deleted old trajectory file #{trajfile}" - ) - finally: - pass - - - def __sorter(self, item): - r''' - Returns the time step of a dump file that is given - in the filename of the dump file. - - INPUT: - item: dump-file name (str) - - OUTPUT: - time step (float) - ''' - key = self.__dumps.split('*')[0] - basedir, basename = os.path.split(item) - if key=='': - return float(basename.split('.')[0]) - return float(basename.split(key)[1].split('.')[0]) - - - def __get_timestep_from_logfile(self): - r''' - Reads the time step from the log.lammps file. - - Returns - ------- - dt : float - Time step. - ''' - if os.path.exists(os.path.join(self.directory, 'log.lammps')): - with open(os.path.join( - self.directory, 'log.lammps' - ), encoding="utf-8") as f: - lines = f.readlines() - relevant = [line for line in lines if line.startswith('timestep')] - if len(relevant)>=1: - dt = float(relevant[-1].split('timestep')[-1]) - else: - dt = 1 - self.__log.warning( - "No timestep mentioned in logfile. Using dt=1. "\ - "The timestep can be set manually by traj.dt=
." - ) - if len(relevant)>1: - self.__log.warning( - "More than one timestep found. Using last mention, "\ - f"dt={dt}. "\ - "The timestep can be set manually by traj.dt=
." - ) - else: - self.__log.warning( - "No log file found. Using dt=1. "\ - "The timestep can be set manually by traj.dt=
." - ) - dt = 1 - - return dt + return delimiter \ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py index b2145f1..56891cf 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -9,7 +9,7 @@ project = 'AMEP' copyright = '2023-2024, Lukas Hecht, Kay-Robert Dormann, Kai Luca Spanheimer' author = 'Lukas Hecht, Kay-Robert Dormann, Kai Luca Spanheimer' -release = '1.0.2' +release = '1.0.3' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration @@ -51,7 +51,7 @@ "url": "https://github.com/amepproject/amep", # required # Icon class (if "type": "fontawesome"), or path to local image (if "type": "local") "icon": "fa-brands fa-square-github", - # The type of image to be used (see below for details) + # The type of image to be used "type": "fontawesome", }, { @@ -61,8 +61,16 @@ "url": "https://pypi.org/project/amep/", # required # Icon class (if "type": "fontawesome"), or path to local image (if "type": "local") "icon": "fa-brands fa-python", - # The type of image to be used (see below for details) + # The type of image to be used "type": "fontawesome", } - ] - } + ], + #"switcher": { + # "json_url": "https://amepproject.de/switcher.json", + # "version_match": release + #}, + #"check_switcher": True, + #"navbar_persistent": ["search-button.html", "theme-switcher.html"], + #"navbar_end": ["version-switcher.html", "icon-links.html"] + # "navbar_start": ["navbar_logo", "version-switcher"] + } diff --git a/doc/source/datastructures/particle_data_reader.md b/doc/source/datastructures/particle_data_reader.md deleted file mode 100644 index e77ffe4..0000000 --- a/doc/source/datastructures/particle_data_reader.md +++ /dev/null @@ -1,20 +0,0 @@ -# Creating a new data reader - -If you want to implement a reader for a particle based simulation data format. -You need to read out the following data: - - -For each timestep: -1. Timestep -2. Simulation time -3. Simulation Box -4. Center of the Box -5. particle coordinates -6. Unwrapped particle coordinates -7. particle velocities -8. forces -9. orientations -10. rotation speeds -11. torques -12. Angular momenta - diff --git a/doc/source/user_guide/howto_cite.rst b/doc/source/howto_cite.rst similarity index 95% rename from doc/source/user_guide/howto_cite.rst rename to doc/source/howto_cite.rst index d423787..0dac878 100644 --- a/doc/source/user_guide/howto_cite.rst +++ b/doc/source/howto_cite.rst @@ -1,6 +1,6 @@ -===================== -How to cite **AMEP** -===================== +============ +How to Cite +============ If you use **AMEP** for a project that leads to a scientific publication, please acknowledge the use of **AMEP** within the body of your publication for example by copying or adapting diff --git a/doc/source/index.rst b/doc/source/index.rst index 26b512e..8d63828 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -3,8 +3,8 @@ :align: center -AMEP documentation -================== +AMEP 1.0.3 documentation +======================== .. include:: ../../README.md :parser: myst_parser.sphinx_ @@ -17,7 +17,7 @@ Table of Contents :maxdepth: 2 gettingstarted/index - datastructures/index + howto_cite user_guide/index api diff --git a/doc/source/datastructures/field_data.rst b/doc/source/user_guide/datastructures/field_data.rst similarity index 100% rename from doc/source/datastructures/field_data.rst rename to doc/source/user_guide/datastructures/field_data.rst diff --git a/doc/source/datastructures/function_flow.rst b/doc/source/user_guide/datastructures/function_flow.rst similarity index 100% rename from doc/source/datastructures/function_flow.rst rename to doc/source/user_guide/datastructures/function_flow.rst diff --git a/doc/source/datastructures/index.rst b/doc/source/user_guide/datastructures/index.rst similarity index 84% rename from doc/source/datastructures/index.rst rename to doc/source/user_guide/datastructures/index.rst index 1af028d..0a3fb5c 100644 --- a/doc/source/datastructures/index.rst +++ b/doc/source/user_guide/datastructures/index.rst @@ -11,7 +11,3 @@ the sections below. .. toctree:: field_data - -.. toctree:: - - particle_data_reader diff --git a/doc/source/user_guide/index.rst b/doc/source/user_guide/index.rst index e2b9bfd..fa39e0f 100644 --- a/doc/source/user_guide/index.rst +++ b/doc/source/user_guide/index.rst @@ -15,7 +15,7 @@ your research. .. toctree:: - howto_cite + datastructures/index .. toctree::