diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index d252156..66ee5bf 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -1,5 +1,12 @@ name: Build, test and publish to (test)PyPI -on: push +on: + push: + tags: + - 'v*' # matches semantic versioning (e.g., v1.2.3) + branches: + - main + - master + - dev jobs: build: @@ -43,6 +50,11 @@ jobs: publish-to-testpypi: name: Publish to TestPyPI + # Only on dev pushes + # if: ${{ github.event_name == 'push' && ( github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/master' ) }} + # Run on untagged push + # if: ${{ !startsWith(github.ref, 'refs/tags/') }} + if: ${{ github.ref == 'refs/heads/dev' }} needs: - build runs-on: ubuntu-latest diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..e952a46 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,35 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + # You can also specify other tool versions: + # nodejs: "20" + # rust: "1.70" + # golang: "1.20" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/source/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index e860880..b74f208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ -* v5.2.1 +* 5.2.4 + - FHI interface + - New spglib api used (pinned >= 2.5.0) +* 5.2.3 + - Fix RTD build config +* 5.2.2 + - Re-activate RTD build +* 5.2.1 - Fix wrong coefficients in the hexagonal structure - Fix wrong Cij names in tetragonal structure - Update use of new spglib API -* v5.2.0 +* 5.2.0 - New build infrastructure (github actions) - Changelog ;) diff --git a/README.md b/README.md index 5fa01d8..809f9f8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Elastic ![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/elastic?label=forge) ![GitHub License](https://img.shields.io/github/license/jochym/Elastic) [![DOI](https://zenodo.org/badge/doi/10.5281/zenodo.593721.svg)](https://doi.org/10.5281/zenodo.593721) -[![Documentation Status](https://readthedocs.org/projects/elastic/badge/?version=latest)](https://elastic.readthedocs.io/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/elastic/badge/?version=latest)](https://elastic.readthedocs.io/latest) Elastic is a set of python routines for calculation of elastic properties of crystals (elastic constants, equation of state, sound diff --git a/docs/index.md b/docs/index.md index 06faad4..5fa01d8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,18 +1,13 @@ Elastic ======= -[![Build -Status](https://travis-ci.org/jochym/Elastic.svg?branch=master)](https://travis-ci.org/jochym/Elastic) -[![PVersion -Badge](https://img.shields.io/pypi/v/elastic.svg)](https://pypi.org/project/elastic/) -[![CVersion -Badge](https://anaconda.org/conda-forge/elastic/badges/version.svg)](https://anaconda.org/conda-forge/elastic) -[![Downloads -Badge](https://anaconda.org/conda-forge/elastic/badges/downloads.svg)](https://anaconda.org/conda-forge/elastic) -[![License -Badge](https://anaconda.org/jochym/elastic/badges/license.svg)](https://anaconda.org/jochym/elastic) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/jochym/Elastic/pypi.yml) +![PyPI - Version](https://img.shields.io/pypi/v/elastic) +![PyPI - Downloads](https://img.shields.io/pypi/dm/elastic?label=PyPi) +![Conda Version](https://img.shields.io/conda/vn/conda-forge/elastic) +![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/elastic?label=forge) +![GitHub License](https://img.shields.io/github/license/jochym/Elastic) [![DOI](https://zenodo.org/badge/doi/10.5281/zenodo.593721.svg)](https://doi.org/10.5281/zenodo.593721) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/9f348d724d564ce399beb009ab9a3654)](https://www.codacy.com/app/jochym/Elastic?utm_source=github.com&utm_medium=referral&utm_content=jochym/Elastic&utm_campaign=badger) [![Documentation Status](https://readthedocs.org/projects/elastic/badge/?version=latest)](https://elastic.readthedocs.io/en/latest/?badge=latest) Elastic is a set of python routines for calculation of elastic @@ -33,11 +28,6 @@ website](http://wolf.ifj.edu.pl/elastic/). You can obtain the file](https://media.readthedocs.org/pdf/elastic/stable/elastic.pdf) as well. -The docs are also published at: [Elastic over -IPFS](https://ipfs.io/ipns/QmSHUr59SLvWEZq7URTDGgouVaq7vFJYG7HqtBgL4s6M2u/) - -The stable site hash is: -/ipns/QmSHUr59SLvWEZq7URTDGgouVaq7vFJYG7HqtBgL4s6M2u/ Installation ------------ diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..6c5d5d4 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +sphinx-rtd-theme diff --git a/docs/source/cli-usage.rst b/docs/source/cli-usage.rst index 6db4db8..6527e7d 100644 --- a/docs/source/cli-usage.rst +++ b/docs/source/cli-usage.rst @@ -3,8 +3,8 @@ Installation .. image:: https://anaconda.org/conda-forge/elastic/badges/version.svg :target: https://anaconda.org/conda-forge/elastic -.. image:: https://anaconda.org/conda-forge/elastic/badges/installer/conda.svg - :target: https://conda.anaconda.org/conda-forge +.. image:: https://anaconda.org/conda-forge/elastic/badges/latest_release_date.svg + :target: https://anaconda.org/conda-forge/elastic .. image:: https://anaconda.org/conda-forge/elastic/badges/platforms.svg :target: https://anaconda.org/conda-forge/elastic diff --git a/elastic/cli/elastic.py b/elastic/cli/elastic.py index a140339..9889f07 100755 --- a/elastic/cli/elastic.py +++ b/elastic/cli/elastic.py @@ -53,6 +53,8 @@ def process_calc(fn): help='Use VASP formats (default)', default=True) @click.option('--abinit', 'frmt', flag_value='abinit', help='Use AbInit formats') +@click.option('--aims', 'frmt', flag_value='aims', + help='Use FHI-aims formats') @click.option('--cij', 'action', flag_value='cij', help='Generate deformations for Cij (default)', default=True) @click.option('--eos', 'action', flag_value='eos', @@ -92,6 +94,9 @@ def gen(ctx, num, lo, hi, size, struct): elif frmt == 'abinit': fn_tmpl += '_%03d.abinit' kwargs = {} + elif frmt == "aims": + fn_tmpl += '_%03d.in' + kwargs = {} if verbose: from elastic.elastic import get_lattice_type diff --git a/elastic/elastic.py b/elastic/elastic.py index b6ee41e..50087a2 100644 --- a/elastic/elastic.py +++ b/elastic/elastic.py @@ -63,7 +63,6 @@ ''' from __future__ import print_function, division, absolute_import -import re from ase.atoms import Atoms try: @@ -331,12 +330,10 @@ def get_lattice_type(cryst): [231, "Cubic"] ] - sg = spg.get_spacegroup((cryst.get_cell(), - cryst.get_positions(), - cryst.numbers)) - m = re.match(r'([A-Z].*\b)\s*\(([0-9]*)\)', sg) - sg_name = m.group(1) - sg_nr = int(m.group(2)) + cell = (cryst.cell, cryst.get_scaled_positions(), cryst.numbers) + dataset = spg.get_symmetry_dataset(cell) + sg_name = dataset.international + sg_nr = dataset.number for n, l in enumerate(lattice_types): if sg_nr < l[0]: diff --git a/parcalc/parcalc.py b/parcalc/parcalc.py index c1b28fb..c540bd9 100644 --- a/parcalc/parcalc.py +++ b/parcalc/parcalc.py @@ -206,8 +206,8 @@ def run(self): Blocking/Non-blocing run method. In blocking mode it just runs parent run method. In non-blocking mode it raises the __NonBlockingRunException - to bail out of the processing of standard calculate method - (or any other method in fact) and signal that the data is not + to bail out of the processing of standard calculate method + (or any other method in fact) and signal that the data is not ready to be collected. ''' # This is only called from self.calculate - thus @@ -302,7 +302,7 @@ class RemoteCalculator(Calculator): ''' Remote calculator based on ASE calculator class. This class is only involved with the machanics of remotly executing - the software and transporting the data. The calculation is + the software and transporting the data. The calculation is delegated to the actual calculator class. ''' @@ -315,7 +315,7 @@ class RemoteCalculator(Calculator): # Remote execution command remote_exec_cmd='ssh %(user)s@%(host)s "%(command)s"' - # If you cannot mount the data directory into your system it is best + # If you cannot mount the data directory into your system it is best # to use the rsync command to transfer the results back into the system. # Command for copying the data out to the computing system @@ -358,7 +358,7 @@ def __init__(self, restart=None, ignore_bad_restart_file=False, label=None, attached. When restarting, atoms will get its positions and unit-cell updated from file. - Create a remote execution calculator based on actual ASE calculator + Create a remote execution calculator based on actual ASE calculator calc. ''' logging.debug("Calc: %s Label: %s" % (calc, label)) @@ -374,25 +374,35 @@ def write_pbs_in(self,properties): fh.write(self.pbs_template % { 'command': self.build_command(self,prop=properties, params=self.parameters) - }) + }) - def build_command(self,prop=['energy'],params={}): + def build_command(self, prop=None, params=None): + ''' + Build command strings for local or remote execution + ''' + if prop is None : + prop = ['energy'] + if params is None : + params = {} cmd=self.qsub_cmd % { - 'qsub_tool': self.qsub_tool, - 'qstat_tool': self.qstat_tool, - 'title': self.label, - 'procs': self.parameters['procs'], - 'rdir': os.path.join(self.parameters['rdir'],os.path.split(self.directory)[-1]) - } + 'qsub_tool': self.qsub_tool, + 'qstat_tool': self.qstat_tool, + 'title': self.label, + 'procs': self.parameters['procs'], + 'rdir': os.path.join(self.parameters['rdir'], + os.path.split(self.directory)[-1]) + } cmd=self.remote_exec_cmd % { 'command': cmd, 'user': self.parameters['user'], 'host': self.parameters['host'] - } + } return cmd - def write_input(self, atoms=None, properties=['energy'], system_changes=all_changes): + def write_input(self, atoms=None, properties=None, system_changes=all_changes): '''Write input file(s).''' + if properties is None : + properties = ['energy'] with work_dir(self.directory): self.calc.write_input(self, atoms, properties, system_changes) self.write_pbs_in(properties) @@ -422,17 +432,19 @@ def job_ready(self): return not (state in ['Q','R']) - def run_calculation(self, atoms=None, properties=['energy'], + def run_calculation(self, atoms=None, properties=None, system_changes=all_changes): ''' Internal calculation executor. We cannot use FileIOCalculator directly since we need to support remote execution. - This calculator is different from others. + This calculator is different from others. It prepares the directory, launches the remote process and raises the exception to signal that we need to come back for results when the job is finished. ''' + if properties is None : + properties = ['energy'] self.calc.calculate(self, atoms, properties, system_changes) self.write_input(self.atoms, properties, system_changes) if self.command is None: @@ -481,7 +493,7 @@ def read_results(self): fn=os.path.join(self.directory,'pw.out') # Read the pan-ultimate line of the output file - try: + try: ln=open(fn).readlines()[-2] if ln.find('JOB DONE.')>-1 : # Job is done we can read the output @@ -494,19 +506,21 @@ def read_results(self): except (IOError, IndexError) : # Job not ready. raise CalcNotReadyError - + # All is fine - really read the results self.calc.read_results(self) @classmethod - def ParallelCalculate(cls,syslst,properties=['energy'],system_changes=all_changes): + def ParallelCalculate(cls,syslst,properties=None,system_changes=all_changes): ''' - Run a series of calculations in parallel using (implicitely) some + Run a series of calculations in parallel using (implicitely) some remote machine/cluster. The function returns the list of systems ready for the extraction of calculated properties. ''' print('Launching:',end=' ') sys.stdout.flush() + if properties is None : + properties = ['energy'] for n,s in enumerate(syslst): try : s.calc.block=False @@ -582,10 +596,10 @@ def ParCalculate(systems,calc,cleanup=True,block=True,prefix="Calc_"): The resulting objects are returned in the list (one per input system). ''' - if type(systems) != type([]) : - sysl=[systems] + if isinstance(systems, list) : + sysl = systems else : - sysl=systems + sysl = [systems] if block : iq=Queue(len(sysl)+1) diff --git a/requirements.txt b/requirements.txt index 6b038c9..42eed36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ ase -spglib +spglib >= 2.5.0 numpy scipy setuptools_scm diff --git a/setup.py b/setup.py index 724936f..e5a4c75 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,16 @@ def current_version(version: ScmVersion) -> str: from setuptools_scm.version import guess_next_version + delim = '.' + if version.branch in ("master", "main"): + delim = '.' + if version.branch in ("dev", "devel"): + delim = '-dev' + if version.branch in ("fix", "hotfix"): + delim = '-post' + return version.format_next_version(guess_next_version, - "{tag}.{distance}") + "{tag}"+ delim +"{distance}") setup(use_scm_version={"version_scheme": current_version,