diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 8d74da6b..e88c52fd 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -12,11 +12,11 @@ jobs: build-linux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.8 + - uses: actions/checkout@v3 + - name: Set up Python 3.12 uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.12 - name: Add conda to system path run: | # $CONDA is an environment variable pointing to the root of the miniconda directory @@ -24,10 +24,10 @@ jobs: - name: Install dependencies shell: bash -l {0} run: | - conda install python=3.8 - conda install numpy + conda install python=3.12 conda install -c conda-forge gpy - conda install pytorch + conda install numpy + conda install -c conda-forge pytorch=2.1.2 conda install pandas conda install -c conda-forge cclib conda install -c conda-forge hyperopt @@ -48,7 +48,7 @@ jobs: matrix: cfg: - conda-env: docs-cf - python-version: 3.8 + python-version: 3.12 label: Sphinx runs-on: ubuntu-latest @@ -59,13 +59,13 @@ jobs: - uses: actions/checkout@v3 - name: Create Environment - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v3 with: activate-environment: test environment-file: docs/requirements.yml python-version: ${{ matrix.cfg.python-version }} auto-activate-base: false - miniforge-variant: Mambaforge + miniforge-variant: Miniforge3 miniforge-version: latest use-mamba: true add-pip-as-python-dependency: true @@ -74,4 +74,4 @@ jobs: - name: Environment Information run: | mamba info - mamba list --show-channel-urls \ No newline at end of file + mamba list \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4315282f..53bde822 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,12 +20,12 @@ jobs: uses: actions/checkout@v3 - name: Create Environment - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v3 with: activate-environment: docs-env environment-file: docs/requirements.yml python-version: ${{ matrix.cfg.python-version }} - miniforge-variant: Mambaforge + miniforge-variant: Miniforge3 use-mamba: true add-pip-as-python-dependency: true channels: conda-forge diff --git a/docs/requirements.yml b/docs/requirements.yml index 0846dcd6..44a7d651 100644 --- a/docs/requirements.yml +++ b/docs/requirements.yml @@ -3,7 +3,7 @@ channels: - nodefaults - conda-forge dependencies: - - python=3.8 + - python=3.12 - sphinx - sphinx_rtd_theme - sphinx-book-theme @@ -13,11 +13,11 @@ dependencies: - autodoc-pydantic # PESLearn depends + - GPy >=1.9 - numpy - pydantic >=0.30.1 - qcelemental >=0.9.0 - numpy >=1.7 - - GPy >=1.9 - scikit-learn >=0.20 - pandas >=0.24 - hyperopt >=0.1.1 diff --git a/peslearn/constants.py b/peslearn/constants.py index b3e3a119..76eceeec 100644 --- a/peslearn/constants.py +++ b/peslearn/constants.py @@ -180,3 +180,41 @@ def cart1d_to_distances1d(vec): distance_vector = distance_matrix[np.tril_indices(len(distance_matrix),-1)] return distance_vector """ + +gradient_nn_convenience_function = """ +# how to use 'gradient_compute()' function +# -------------------------------------- +# grads = gradient_compute(cartesian_dataset_path) +# 'cartesian_dataset_path' is a path to a dataset with cartesian coordinates that will be used with model to predict gradients. +# Cartesian geometries should contain atomic symbol (e.g. H, C, Br, etc.) followed by XYZ cartesian coordinates each separated by spaces. +# The dataset may contain one or multiple geometries, if given multiple geometries it will return a list of multiple gradients +# The output of the 'gradient_compute()' function will be the negative derivative of the predicted energy from the model with respect to +# the cartesian coordinate of the provided geometry. +# i.e. ouput = -dE/dq, where output is the returned value from the 'gradient_compute()' function, E is the model predicted energy, and q are the cartesian coordinates +# Input coordinates need not be in standard order, they will however be transformed into standard order for the output. +# Standard order in PES-Learn lists most common atoms first with alphabetical tiebreakers. +# e.g. If the input provided for water lists the XYZ coordinates in order of O H1 H2, then the output gradient will be in the order H1 H2 O. +# The returned gradients will be in units of Hartree/distance where distance is either Bohr or Angstrom. +# The distance unit used to construct the model should be the same distance unit used to predict gradients. +# Outputs are of the form of a list of torch tensors, where each tensor is a predicted gradient in the order provided in the catesian dataset. +# Using this function for the first time creates a new file containing a function tailored to the given input molecule. + +def gradient_compute(cart_dataset_path): + grad = [] + # transform + sorted_atoms, geoms = geometry_transform_helper.load_cartesian_dataset(cart_dataset_path, no_energy=True) + for i in range(len(geoms)): + geoms[i] = geometry_transform_helper.remove_atom_labels(geoms[i]) + if not os.path.exists('grad_func.py'): + geometry_transform_helper.write_grad_input(sorted_atoms) + from grad_func import gradient_prediction + for g in range(len(geoms)): + grad.append(gradient_prediction(nn, params, model, Xscaler, yscaler, geoms[g])) + return grad +""" + + + + + + diff --git a/peslearn/datagen/outputfile.py b/peslearn/datagen/outputfile.py index 29bcdfaa..619a481f 100644 --- a/peslearn/datagen/outputfile.py +++ b/peslearn/datagen/outputfile.py @@ -24,7 +24,7 @@ def __init__(self, output_path): self.output_str = f.read() def extract_energy_with_regex(self, energy_regex): - """ + r""" Finds the energy value (a float) in an output file according to a user supplied regular expression identifier. @@ -34,7 +34,7 @@ def extract_energy_with_regex(self, energy_regex): FINAL ELECTRONIC ENERGY (Eh): -2.3564983498 One can obtain this floating point number with the regex identifier: - \s*FINAL ELECTRONIC ENERGY \(Eh\):\s+(-\d+\.\d+) + r"\s*FINAL ELECTRONIC ENERGY \(Eh\):\s+(-\d+\.\d+)" Checking ones regular expression is easy with online utilities such as pythex (see pythex.org) @@ -42,7 +42,7 @@ def extract_energy_with_regex(self, energy_regex): --------- energy_regex : str A string containing the regex code for capturing an energy floating point number. - e.g. "\s*FINAL ELECTRONIC ENERGY \(Eh\):\s+(-\d+\.\d+)" + e.g. r"\s*FINAL ELECTRONIC ENERGY \(Eh\):\s+(-\d+\.\d+)" Returns ------- @@ -126,11 +126,11 @@ def extract_from_schema(self, driver): """ if driver == "energy": energy = [] - energy = re.findall("\s\'return_energy\'\:\s+(-\d+\.\d+)", self.output_str) + energy = re.findall(r"\s\'return_energy\'\:\s+(-\d+\.\d+)", self.output_str) if energy: return energy else: - success = re.findall("\s\'success\'\:\s+(\S+)\}", self.output_str) + success = re.findall(r"\s\'success\'\:\s+(\S+)\}", self.output_str) if success[0] == 'False': energy = 'False' return energy @@ -140,7 +140,7 @@ def extract_from_schema(self, driver): return energy if driver == "gradient": - gradient = re.findall("\s\'return_gradient\'\:\s+array\(([\s\S]*?)\)\,", self.output_str) + gradient = re.findall(r"\s\'return_gradient\'\:\s+array\(([\s\S]*?)\)\,", self.output_str) if gradient: import ast gradient = re.sub(r'\s+', "", str(gradient)) @@ -150,7 +150,7 @@ def extract_from_schema(self, driver): gradient = np.asarray(ast.literal_eval(gradient)).astype(np.float64) return gradient else: - success = re.findall("\s\'success\'\:\s+(\S+)\}", self.output_str) + success = re.findall(r"\s\'success\'\:\s+(\S+)\}", self.output_str) if success[0] == 'False': gradient = 'False' return gradient @@ -160,7 +160,7 @@ def extract_from_schema(self, driver): if driver == "hessian": - hessian = re.findall("\s\'return_hessian\'\:\s+array\(([\s\S]*?)\)\,", self.output_str) + hessian = re.findall(r"\s\'return_hessian\'\:\s+array\(([\s\S]*?)\)\,", self.output_str) if hessian: import ast hessian = re.sub(r'\s+', "", str(hessian)) @@ -170,7 +170,7 @@ def extract_from_schema(self, driver): hessian = np.asarray(ast.literal_eval(hessian)).astype(np.float64) return hessian else: - success = re.findall("\s\'success\'\:\s+(\S+)\}", self.output_str) + success = re.findall(r"\s\'success\'\:\s+(\S+)\}", self.output_str) if success[0] == 'False': hessian = 'False' return hessian @@ -185,7 +185,7 @@ def extract_from_schema(self, driver): return properties def extract_cartesian_gradient_with_regex(self, header, footer, grad_line_regex): - """ + r""" Extracts cartesian gradients according to user supplied regular expressions. A bit more tedious to use than the energy regex extractor as the size of the regular expressions may be quite long. Requires that the electronic structure theory code prints the cartesian gradient in a logical way. @@ -214,7 +214,7 @@ def extract_cartesian_gradient_with_regex(self, header, footer, grad_line_regex) Atom 1 Cl 0.00000 0.23410 0.32398 Atom 2 H 0.02101 0.09233 0.01342 Atom 3 N 0.01531 0.04813 0.06118 - A valid argument for grad_line_regex would be "Atom\s+\d+\s+[A-Z,a-z]+\s+(-?\d+\.\d+)\s+(-?\d+\.\d+)\s+(-?\d+\.\d+)" + A valid argument for grad_line_regex would be r"Atom\s+\d+\s+[A-Z,a-z]+\s+(-?\d+\.\d+)\s+(-?\d+\.\d+)\s+(-?\d+\.\d+)" This can easily be tested with online utilities such as pythex (see pythex.org) Returns ------- diff --git a/peslearn/input_processor.py b/peslearn/input_processor.py index 842b04f7..2d4d98d5 100644 --- a/peslearn/input_processor.py +++ b/peslearn/input_processor.py @@ -17,7 +17,7 @@ class InputProcessor(object): """ def __init__(self, input_string): # Remove all comments denoted by '#' - self.full_string = re.sub('\s*#.+', '', input_string) + self.full_string = re.sub(r'\s*#.+', '', input_string) if re.search(regex.intcoords_regex, self.full_string): self.zmat_string = re.findall(regex.intcoords_regex, self.full_string)[0] self.intcos_ranges = None @@ -81,7 +81,7 @@ def get_keywords(self): } for k in string_keywords: - match = re.search(k+"\s*=\s*(.+)", self.full_string) + match = re.search(k + r"\s*=\s*(.+)", self.full_string) # if the keyword is mentioned if match: value = str(match.group(1)) @@ -90,7 +90,7 @@ def get_keywords(self): if k not in regex_keywords: value = value.lower().strip() # if keyword is raw text, add quotes so it is a string - if re.match("[a-z\_]+", value): + if re.match(r"[a-z\_]+", value): if (r"'" or r'"') not in value: value = "".join((r'"',value,r'"',)) try: @@ -114,11 +114,11 @@ def extract_intcos_ranges(self): # for every geometry label look for its range identifer, e.g. R1 = [0.5, 1.2, 25] for label in geomlabels: # check to make sure parameter isn't defined more than once - if len(re.findall("\W" + label+"\s*=\s*", self.full_string)) > 1: + if len(re.findall(r"\W" + label + r"\s*=\s*", self.full_string)) > 1: raise Exception("Parameter {} defined more than once.".format(label)) # if geom parameter has a geometry range, save it - match = re.search(label+"\s*=\s*(\[.+\])", self.full_string) + match = re.search(label + r"\s*=\s*(\[.+\])", self.full_string) if match: try: ranges[label] = ast.literal_eval(match.group(1)) @@ -126,7 +126,7 @@ def extract_intcos_ranges(self): raise Exception("Something wrong with definition of parameter {} in input. Should be of the form [start, stop, # of points] or a fixed value".format(label)) # if it has a fixed value, save it else: - match = re.search(label+"\s*=\s*(-?\d+\.?\d*)", self.full_string) + match = re.search(label + r"\s*=\s*(-?\d+\.?\d*)", self.full_string) if not match: raise Exception("\nDefinition of parameter {} not found in geometry input. \ \nThe definition is either missing or improperly formatted".format(label)) diff --git a/peslearn/lib/A2/__init__.py b/peslearn/lib/A2/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2/output b/peslearn/lib/A2/output.py similarity index 100% rename from peslearn/lib/A2/output rename to peslearn/lib/A2/output.py diff --git a/peslearn/lib/A2B/__init__.py b/peslearn/lib/A2B/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2B/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2B/output b/peslearn/lib/A2B/output.py similarity index 100% rename from peslearn/lib/A2B/output rename to peslearn/lib/A2B/output.py diff --git a/peslearn/lib/A2B2/__init__.py b/peslearn/lib/A2B2/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2B2/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2B2/output b/peslearn/lib/A2B2/output.py similarity index 100% rename from peslearn/lib/A2B2/output rename to peslearn/lib/A2B2/output.py diff --git a/peslearn/lib/A2B2C/__init__.py b/peslearn/lib/A2B2C/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2B2C/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2B2C/output b/peslearn/lib/A2B2C/output.py similarity index 100% rename from peslearn/lib/A2B2C/output rename to peslearn/lib/A2B2C/output.py diff --git a/peslearn/lib/A2B2C2/__init__.py b/peslearn/lib/A2B2C2/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2B2C2/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2B2C2/output b/peslearn/lib/A2B2C2/output.py similarity index 100% rename from peslearn/lib/A2B2C2/output rename to peslearn/lib/A2B2C2/output.py diff --git a/peslearn/lib/A2B2C2D/__init__.py b/peslearn/lib/A2B2C2D/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2B2C2D/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2B2C2D/output b/peslearn/lib/A2B2C2D/output.py similarity index 100% rename from peslearn/lib/A2B2C2D/output rename to peslearn/lib/A2B2C2D/output.py diff --git a/peslearn/lib/A2B2C2D2/__init__.py b/peslearn/lib/A2B2C2D2/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2B2C2D2/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2B2C2D2/output b/peslearn/lib/A2B2C2D2/output.py similarity index 100% rename from peslearn/lib/A2B2C2D2/output rename to peslearn/lib/A2B2C2D2/output.py diff --git a/peslearn/lib/A2B2C2DE/__init__.py b/peslearn/lib/A2B2C2DE/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2B2C2DE/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2B2C2DE/output b/peslearn/lib/A2B2C2DE/output.py similarity index 100% rename from peslearn/lib/A2B2C2DE/output rename to peslearn/lib/A2B2C2DE/output.py diff --git a/peslearn/lib/A2B2CD/__init__.py b/peslearn/lib/A2B2CD/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2B2CD/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2B2CD/output b/peslearn/lib/A2B2CD/output.py similarity index 100% rename from peslearn/lib/A2B2CD/output rename to peslearn/lib/A2B2CD/output.py diff --git a/peslearn/lib/A2B2CDE/__init__.py b/peslearn/lib/A2B2CDE/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2B2CDE/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2B2CDE/output b/peslearn/lib/A2B2CDE/output.py similarity index 100% rename from peslearn/lib/A2B2CDE/output rename to peslearn/lib/A2B2CDE/output.py diff --git a/peslearn/lib/A2B2CDEF/__init__.py b/peslearn/lib/A2B2CDEF/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2B2CDEF/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2B2CDEF/output b/peslearn/lib/A2B2CDEF/output.py similarity index 100% rename from peslearn/lib/A2B2CDEF/output rename to peslearn/lib/A2B2CDEF/output.py diff --git a/peslearn/lib/A2BC/__init__.py b/peslearn/lib/A2BC/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2BC/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2BC/output b/peslearn/lib/A2BC/output.py similarity index 100% rename from peslearn/lib/A2BC/output rename to peslearn/lib/A2BC/output.py diff --git a/peslearn/lib/A2BCD/__init__.py b/peslearn/lib/A2BCD/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2BCD/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2BCD/output b/peslearn/lib/A2BCD/output.py similarity index 100% rename from peslearn/lib/A2BCD/output rename to peslearn/lib/A2BCD/output.py diff --git a/peslearn/lib/A2BCDE/__init__.py b/peslearn/lib/A2BCDE/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2BCDE/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2BCDE/output b/peslearn/lib/A2BCDE/output.py similarity index 100% rename from peslearn/lib/A2BCDE/output rename to peslearn/lib/A2BCDE/output.py diff --git a/peslearn/lib/A2BCDEF/__init__.py b/peslearn/lib/A2BCDEF/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2BCDEF/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2BCDEF/output b/peslearn/lib/A2BCDEF/output.py similarity index 100% rename from peslearn/lib/A2BCDEF/output rename to peslearn/lib/A2BCDEF/output.py diff --git a/peslearn/lib/A2BCDEFG/__init__.py b/peslearn/lib/A2BCDEFG/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A2BCDEFG/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A2BCDEFG/output b/peslearn/lib/A2BCDEFG/output.py similarity index 100% rename from peslearn/lib/A2BCDEFG/output rename to peslearn/lib/A2BCDEFG/output.py diff --git a/peslearn/lib/A3/__init__.py b/peslearn/lib/A3/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3/output b/peslearn/lib/A3/output.py similarity index 100% rename from peslearn/lib/A3/output rename to peslearn/lib/A3/output.py diff --git a/peslearn/lib/A3B/__init__.py b/peslearn/lib/A3B/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3B/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3B/output b/peslearn/lib/A3B/output.py similarity index 100% rename from peslearn/lib/A3B/output rename to peslearn/lib/A3B/output.py diff --git a/peslearn/lib/A3B2/__init__.py b/peslearn/lib/A3B2/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3B2/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3B2/output b/peslearn/lib/A3B2/output.py similarity index 100% rename from peslearn/lib/A3B2/output rename to peslearn/lib/A3B2/output.py diff --git a/peslearn/lib/A3B2C/__init__.py b/peslearn/lib/A3B2C/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3B2C/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3B2C/output b/peslearn/lib/A3B2C/output.py similarity index 100% rename from peslearn/lib/A3B2C/output rename to peslearn/lib/A3B2C/output.py diff --git a/peslearn/lib/A3B2C2/__init__.py b/peslearn/lib/A3B2C2/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3B2C2/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3B2C2/output b/peslearn/lib/A3B2C2/output.py similarity index 100% rename from peslearn/lib/A3B2C2/output rename to peslearn/lib/A3B2C2/output.py diff --git a/peslearn/lib/A3B2C2D/__init__.py b/peslearn/lib/A3B2C2D/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3B2C2D/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3B2C2D/output b/peslearn/lib/A3B2C2D/output.py similarity index 100% rename from peslearn/lib/A3B2C2D/output rename to peslearn/lib/A3B2C2D/output.py diff --git a/peslearn/lib/A3B2CD/__init__.py b/peslearn/lib/A3B2CD/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3B2CD/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3B2CD/output b/peslearn/lib/A3B2CD/output.py similarity index 100% rename from peslearn/lib/A3B2CD/output rename to peslearn/lib/A3B2CD/output.py diff --git a/peslearn/lib/A3B2CDE/__init__.py b/peslearn/lib/A3B2CDE/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3B2CDE/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3B2CDE/output b/peslearn/lib/A3B2CDE/output.py similarity index 100% rename from peslearn/lib/A3B2CDE/output rename to peslearn/lib/A3B2CDE/output.py diff --git a/peslearn/lib/A3B3/__init__.py b/peslearn/lib/A3B3/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3B3/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3B3/output b/peslearn/lib/A3B3/output.py similarity index 100% rename from peslearn/lib/A3B3/output rename to peslearn/lib/A3B3/output.py diff --git a/peslearn/lib/A3B3C/__init__.py b/peslearn/lib/A3B3C/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3B3C/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3B3C/output b/peslearn/lib/A3B3C/output.py similarity index 100% rename from peslearn/lib/A3B3C/output rename to peslearn/lib/A3B3C/output.py diff --git a/peslearn/lib/A3B3CD/__init__.py b/peslearn/lib/A3B3CD/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3B3CD/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3B3CD/output b/peslearn/lib/A3B3CD/output.py similarity index 100% rename from peslearn/lib/A3B3CD/output rename to peslearn/lib/A3B3CD/output.py diff --git a/peslearn/lib/A3BC/__init__.py b/peslearn/lib/A3BC/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3BC/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3BC/output b/peslearn/lib/A3BC/output.py similarity index 100% rename from peslearn/lib/A3BC/output rename to peslearn/lib/A3BC/output.py diff --git a/peslearn/lib/A3BCD/__init__.py b/peslearn/lib/A3BCD/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3BCD/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3BCD/output b/peslearn/lib/A3BCD/output.py similarity index 100% rename from peslearn/lib/A3BCD/output rename to peslearn/lib/A3BCD/output.py diff --git a/peslearn/lib/A3BCDE/__init__.py b/peslearn/lib/A3BCDE/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3BCDE/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3BCDE/output b/peslearn/lib/A3BCDE/output.py similarity index 100% rename from peslearn/lib/A3BCDE/output rename to peslearn/lib/A3BCDE/output.py diff --git a/peslearn/lib/A3BCDEF/__init__.py b/peslearn/lib/A3BCDEF/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A3BCDEF/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A3BCDEF/output b/peslearn/lib/A3BCDEF/output.py similarity index 100% rename from peslearn/lib/A3BCDEF/output rename to peslearn/lib/A3BCDEF/output.py diff --git a/peslearn/lib/A4/__init__.py b/peslearn/lib/A4/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A4/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A4/output b/peslearn/lib/A4/output.py similarity index 100% rename from peslearn/lib/A4/output rename to peslearn/lib/A4/output.py diff --git a/peslearn/lib/A4B/__init__.py b/peslearn/lib/A4B/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A4B/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A4B/output b/peslearn/lib/A4B/output.py similarity index 100% rename from peslearn/lib/A4B/output rename to peslearn/lib/A4B/output.py diff --git a/peslearn/lib/A4B2/__init__.py b/peslearn/lib/A4B2/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A4B2/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A4B2/output b/peslearn/lib/A4B2/output.py similarity index 100% rename from peslearn/lib/A4B2/output rename to peslearn/lib/A4B2/output.py diff --git a/peslearn/lib/A4B2C/__init__.py b/peslearn/lib/A4B2C/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A4B2C/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A4B2C/output b/peslearn/lib/A4B2C/output.py similarity index 100% rename from peslearn/lib/A4B2C/output rename to peslearn/lib/A4B2C/output.py diff --git a/peslearn/lib/A4B2CD/__init__.py b/peslearn/lib/A4B2CD/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A4B2CD/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A4B2CD/output b/peslearn/lib/A4B2CD/output.py similarity index 100% rename from peslearn/lib/A4B2CD/output rename to peslearn/lib/A4B2CD/output.py diff --git a/peslearn/lib/A4B3C/__init__.py b/peslearn/lib/A4B3C/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A4B3C/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A4B3C/output b/peslearn/lib/A4B3C/output.py similarity index 100% rename from peslearn/lib/A4B3C/output rename to peslearn/lib/A4B3C/output.py diff --git a/peslearn/lib/A4BC/__init__.py b/peslearn/lib/A4BC/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A4BC/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A4BC/output b/peslearn/lib/A4BC/output.py similarity index 100% rename from peslearn/lib/A4BC/output rename to peslearn/lib/A4BC/output.py diff --git a/peslearn/lib/A4BCD/__init__.py b/peslearn/lib/A4BCD/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A4BCD/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A4BCD/output b/peslearn/lib/A4BCD/output.py similarity index 100% rename from peslearn/lib/A4BCD/output rename to peslearn/lib/A4BCD/output.py diff --git a/peslearn/lib/A4BCDE/__init__.py b/peslearn/lib/A4BCDE/__init__.py new file mode 100644 index 00000000..c1f5b23d --- /dev/null +++ b/peslearn/lib/A4BCDE/__init__.py @@ -0,0 +1 @@ +from . import output \ No newline at end of file diff --git a/peslearn/lib/A4BCDE/output b/peslearn/lib/A4BCDE/output.py similarity index 100% rename from peslearn/lib/A4BCDE/output rename to peslearn/lib/A4BCDE/output.py diff --git a/peslearn/ml/gaussian_process.py b/peslearn/ml/gaussian_process.py index 685e5459..2609083a 100644 --- a/peslearn/ml/gaussian_process.py +++ b/peslearn/ml/gaussian_process.py @@ -131,7 +131,7 @@ def preprocess(self, params, raw_X, raw_y): if params['pip']['pip']: # find path to fundamental invariants form molecule type AxByCz... #path = os.path.join(package_directory, "lib", self.molecule_type, "output") - path = os.path.join(fi_dir, self.molecule_type, "output") + path = os.path.join(fi_dir, self.molecule_type, "output.py") raw_X, degrees = interatomics_to_fundinvar(raw_X,path) if params['pip']['degree_reduction']: raw_X = degree_reduce(raw_X, degrees) @@ -185,8 +185,8 @@ def save_model(self, params): print("Saving ML model data...") model_path = "model1_data" while os.path.isdir(model_path): - new = int(re.findall("\d+", model_path)[0]) + 1 - model_path = re.sub("\d+",str(new), model_path) + new = int(re.findall(r"\d+", model_path)[0]) + 1 + model_path = re.sub(r"\d+",str(new), model_path) os.mkdir(model_path) os.chdir(model_path) with open('model.json', 'w') as f: @@ -226,7 +226,7 @@ def transform_new_X(self, newX, params, Xscaler=None): newX = morse(newX, params['morse_transform']['morse_alpha']) if params['pip']['pip']: # find path to fundamental invariants for an N atom system with molecule type AxByCz... - path = os.path.join(package_directory, "lib", self.molecule_type, "output") + path = os.path.join(package_directory, "lib", self.molecule_type, "output.py") newX, degrees = interatomics_to_fundinvar(newX,path) if params['pip']['degree_reduction']: newX = degree_reduce(newX, degrees) diff --git a/peslearn/ml/kernel_ridge_reg.py b/peslearn/ml/kernel_ridge_reg.py index 6ba74af9..cc159726 100644 --- a/peslearn/ml/kernel_ridge_reg.py +++ b/peslearn/ml/kernel_ridge_reg.py @@ -265,7 +265,7 @@ def preprocess(self, params, raw_X, raw_y): raw_X = morse(raw_X, params['morse_transform']['morse_alpha']) if params['pip']['pip']: # find path to fundamental invariants from molecule type AxByCz... - path = os.path.join(fi_dir, self.molecule_type, "output") + path = os.path.join(fi_dir, self.molecule_type, "output.py") raw_X, degrees = interatomics_to_fundinvar(raw_X, path) if params['pip']['degree_reduction']: raw_X = degree_reduce(raw_X, degrees) @@ -285,8 +285,8 @@ def save_model(self, params): print("Saving ML model data...") model_path = "model1_data" while os.path.isdir(model_path): - new = int(re.findall("\d+", model_path)[0]) + 1 - model_path = re.sub("\d+", str(new), model_path) + new = int(re.findall(r"\d+", model_path)[0]) + 1 + model_path = re.sub(r"\d+", str(new), model_path) os.mkdir(model_path) os.chdir(model_path) with open('hyperparameters', 'w') as f: @@ -327,7 +327,7 @@ def transform_new_X(self, newX, params, Xscaler=None): newX = morse(newX, params['morse_transform']['morse_alpha']) if params['pip']['pip']: # find path to fundamental invariants for an N atom subsystem with molecule type AxByCz... - path = os.path.join(package_directory, "lib", self.molecule_type, "output") + path = os.path.join(package_directory, "lib", self.molecule_type, "output.py") newX, degrees = interatomics_to_fundinvar(newX, path) if params['pip']['degree_reduction']: newX = degree_reduce(newX, degrees) diff --git a/peslearn/ml/neural_network.py b/peslearn/ml/neural_network.py index 0d62cc52..1fbda5eb 100644 --- a/peslearn/ml/neural_network.py +++ b/peslearn/ml/neural_network.py @@ -9,7 +9,7 @@ from .model import Model from .data_sampler import DataSampler -from ..constants import hartree2cm, package_directory, nn_convenience_function +from ..constants import hartree2cm, package_directory, nn_convenience_function, gradient_nn_convenience_function from .preprocessing_helper import morse, interatomics_to_fundinvar, degree_reduce, general_scaler from ..utils.printing_helper import hyperopt_complete from sklearn.model_selection import train_test_split @@ -37,10 +37,10 @@ def __init__(self, dataset_path, input_obj, molecule_type=None, molecule=None, t if self.pip: if molecule_type: - path = os.path.join(package_directory, "lib", molecule_type, "output") + path = os.path.join(package_directory, "lib", molecule_type, "output.py") self.inp_dim = len(open(path).readlines()) if molecule: - path = os.path.join(package_directory, "lib", molecule.molecule_type, "output") + path = os.path.join(package_directory, "lib", molecule.molecule_type, "output.py") self.inp_dim = len(open(path).readlines()) else: self.inp_dim = self.raw_X.shape[1] @@ -294,6 +294,7 @@ def build_model(self, params, maxit=1000, val_freq=10, es_patience=2, opt='lbfgs model = model.double() metric = torch.nn.MSELoss() + mae = torch.nn.L1Loss() # Define optimizer if 'lr' in params: lr = params['lr'] @@ -420,7 +421,12 @@ def closure(): full_pred = model(self.X) full_loss = metric(full_pred, self.y) full_error_rmse = np.sqrt(full_loss.item() * loss_descaler) * hartree2cm + test_mae = mae(test_pred, self.ytest) + mae_test = (test_mae.item() * loss_descaler) * hartree2cm + full_mae = mae(full_pred, self.y) + mae_full = (full_mae * loss_descaler) * hartree2cm print("Test set RMSE (cm-1): {:5.2f} Validation set RMSE (cm-1): {:5.2f} Full dataset RMSE (cm-1): {:5.2f}".format(test_error_rmse, val_error_rmse, full_error_rmse)) + print("Test set MAE (cm-1): {:5.2f} Full MAE (cm-1): {:5.2f}".format(mae_test, mae_full)) if return_model: return model, test_error_rmse, val_error_rmse, full_error_rmse else: @@ -452,7 +458,7 @@ def preprocess(self, params, raw_X, raw_y): raw_X = morse(raw_X, params['morse_transform']['morse_alpha']) if params['pip']['pip']: # find path to fundamental invariants form molecule type AxByCz... - path = os.path.join(package_directory, "lib", self.molecule_type, "output") + path = os.path.join(package_directory, "lib", self.molecule_type, "output.py") raw_X, degrees = interatomics_to_fundinvar(raw_X,path) if params['pip']['degree_reduction']: raw_X = degree_reduce(raw_X, degrees) @@ -472,8 +478,8 @@ def save_model(self, params, model, performance): print("Saving ML model data...") model_path = "model1_data" while os.path.isdir(model_path): - new = int(re.findall("\d+", model_path)[0]) + 1 - model_path = re.sub("\d+",str(new), model_path) + new = int(re.findall(r"\d+", model_path)[0]) + 1 + model_path = re.sub(r"\d+",str(new), model_path) os.mkdir(model_path) os.chdir(model_path) torch.save(model, 'model.pt') @@ -497,6 +503,8 @@ def save_model(self, params, model, performance): self.dataset.to_csv('PES.dat', sep=',',index=False,float_format='%12.12f') with open('compute_energy.py', 'w+') as f: print(self.write_convenience_function(), file=f) + with open('compute_gradient.py', 'w+') as g: + print(self.write_gradient_function(), file=g) os.chdir("../") def transform_new_X(self, newX, params, Xscaler=None): @@ -513,13 +521,66 @@ def transform_new_X(self, newX, params, Xscaler=None): newX = morse(newX, params['morse_transform']['morse_alpha']) if params['pip']['pip']: # find path to fundamental invariants for an N atom system with molecule type AxByCz... - path = os.path.join(package_directory, "lib", self.molecule_type, "output") + path = os.path.join(package_directory, "lib", self.molecule_type, "output.py") newX, degrees = interatomics_to_fundinvar(newX,path) if params['pip']['degree_reduction']: newX = degree_reduce(newX, degrees) if Xscaler: newX = Xscaler.transform(newX) return newX + + def torch_transform_new_X(self, newX, params, Xscaler=None): + if len(newX.shape) == 1: + newX = torch.unsqueeze(newX,0) + if params['morse_transform']['morse']: + newX = torch.exp(-newX / params['morse_transform']['morse_alpha']) +# print('dont worry the morse has been morsed') + if Xscaler: + # newx2 = Xscaler.transform(newX) + + def cust_trans(scale_type, data): + """ + feature range is tup of the form (min, max) + """ + + if scale_type == 'std': + u = torch.tensor(Xscaler.mean_, dtype=torch.float32, requires_grad=True) + s = torch.tensor(Xscaler.scale_, dtype=torch.float32, requires_grad=True) +# print(f'u: {u}') +# print(f's: {s}') + xscaled = (data - u) / s + return xscaled + elif scale_type == 'mm01': + feature_range = (0, 1) +# print('mm01 scale type') + elif scale_type == 'mm11': + feature_range = (-1, 1) +# print('mm11 scale type') + min_val = feature_range[0] + max_val = feature_range[1] + # min_val = torch.min(data, axis=0)[0] + # print(f'min_val: {min_val}') + # max_val = torch.max(data, axis=0)[0] + # print(f'max_val: {max_val}') + datamin = torch.tensor(Xscaler.data_min_, dtype=torch.float32, requires_grad=True) + datamax = torch.tensor(Xscaler.data_max_, dtype=torch.float32, requires_grad=True) + # print(f'datamin_X: {datamin}') + # print(f'datamax_X: {datamax}') + xstd = (data - datamin) / (datamax - datamin) + xscaled = xstd * (max_val - min_val) + min_val + return xscaled + + # newX = torch.from_numpy(newX) + newX = cust_trans(params['scale_X']['scale_X'], newX) + # m = torch.mean(newX, keepdim=True) + # s = torch.std(newX, unbiased=False, keepdim=True) + # newX -= m + # newX /= s + # print(f'newx2: {newx2}') + # print(f'newX: {newX}') + # print(f'please be true: {torch.allclose(newX, torch.from_numpy(newx2))}') + return newX + def transform_new_y(self, newy, yscaler=None): if yscaler: @@ -530,7 +591,35 @@ def inverse_transform_new_y(self, newy, yscaler=None): if yscaler: newy = yscaler.inverse_transform(newy) return newy - + + def alt_inverse_new_y(self, newy, params, yscaler=None): + if yscaler: + if params['scale_y'] == 'std': + u = torch.tensor(yscaler.mean_, dtype=torch.float32, requires_grad=True) + s = torch.tensor(yscaler.scale_, dtype=torch.float32, requires_grad=True) + newesty = (newy * s) + u + elif params['scale_y'] == 'mm01': + feature_range = (0, 1) + datamin = torch.tensor(yscaler.data_min_, dtype=torch.float32, requires_grad=True) + datamax = torch.tensor(yscaler.data_max_, dtype=torch.float32, requires_grad=True) + ytemp = (newy - feature_range[0]) / (feature_range[1] - feature_range[0]) + newesty = (ytemp * (datamax - datamin)) + datamin + elif params['scale_y'] == 'mm11': + feature_range = (-1, 1) + datamin = torch.tensor(yscaler.data_min_, dtype=torch.float32, requires_grad=True) + datamax = torch.tensor(yscaler.data_max_, dtype=torch.float32, requires_grad=True) + ytemp = (newy - feature_range[0]) / (feature_range[1] - feature_range[0]) + newesty = (ytemp * (datamax - datamin)) + datamin + return newesty + + def inverse_transform_new_x(self, newX, Xscaler=None): + if Xscaler: + datamin = torch.tensor(Xscaler.data_min_, requires_grad=True) + datamax = torch.tensor(Xscaler.data_max_, requires_grad=True) + xtemp = (newX - -1) / (1 - -1) + newestX = (xtemp * (datamax - datamin)) + datamin + return newestX + def write_convenience_function(self): string = "from peslearn.ml import NeuralNetwork\nfrom peslearn import InputProcessor\nimport torch\nimport numpy as np\nfrom itertools import combinations\n\n" if self.pip: @@ -545,6 +634,17 @@ def write_convenience_function(self): string += nn_convenience_function return string + def write_gradient_function(self): + string = "from peslearn.ml import NeuralNetwork\nfrom peslearn import InputProcessor\nfrom peslearn.utils import geometry_transform_helper\nimport torch\nimport re\nimport os\n\n" + string += "nn = NeuralNetwork('PES.dat', InputProcessor(''))\n" + with open('hyperparameters', 'r') as f: + hyperparameters = f.read() + string += "params = {}\n".format(hyperparameters) + string += "X, y, Xscaler, yscaler = nn.preprocess(params, nn.raw_X, nn.raw_y)\n" + string += "model = torch.load('model.pt')\n" + string += gradient_nn_convenience_function + return string + diff --git a/peslearn/ml/preprocessing_helper.py b/peslearn/ml/preprocessing_helper.py index 4f1be6c4..27fdb5d4 100644 --- a/peslearn/ml/preprocessing_helper.py +++ b/peslearn/ml/preprocessing_helper.py @@ -46,12 +46,12 @@ def interatomics_to_fundinvar(raw_X, fi_path): nbonds = raw_X.shape[1] with open(fi_path, 'r') as f: data = f.read() - data = re.sub('\^', '**', data) + data = re.sub(r'\^', '**', data) # convert subscripts of bonds to 0 indexing for i in range(1, nbonds+1): - data = re.sub('x{}(\D)'.format(str(i)), 'x{}\\1'.format(i-1), data) + data = re.sub(r'x{}(\D)'.format(str(i)), 'x{}\\1'.format(i-1), data) - polys = re.findall("\]=(.+)",data) + polys = re.findall(r"\]=(.+)",data) # create a new_X matrix that is the shape of number geoms, number of Fundamental Invariants new_X = np.zeros((raw_X.shape[0],len(polys))) @@ -67,7 +67,7 @@ def interatomics_to_fundinvar(raw_X, fi_path): # just checking first, assumes every term in each FI polynomial has the same degree (seems to always be true) tmp = p.split('+')[0] # count number of exponents and number of occurances of character 'x' - exps = [int(i) - 1 for i in re.findall("\*\*(\d+)", tmp)] + exps = [int(i) - 1 for i in re.findall(r"\*\*(\d+)", tmp)] ndegrees = len(re.findall("x", tmp)) + sum(exps) degrees.append(ndegrees) diff --git a/peslearn/utils/geometry_transform_helper.py b/peslearn/utils/geometry_transform_helper.py index a8d83faf..f36eaa10 100644 --- a/peslearn/utils/geometry_transform_helper.py +++ b/peslearn/utils/geometry_transform_helper.py @@ -7,7 +7,7 @@ import re import os from itertools import combinations -from .regex import xyz_block_regex,maybe +from .regex import xyz_block_regex, maybe, atom_symbol from ..constants import deg2rad, rad2deg import collections @@ -174,7 +174,7 @@ def get_bond_vector(r, a, d): return bond_vector -def load_cartesian_dataset(xyz_path): +def load_cartesian_dataset(xyz_path, no_energy=False): """ Loads a cartesian dataset with energies on their own line and with standard cartesian coordinates. Reorganizes atoms into standard order (most common elements first, alphabetical tiebreaker) @@ -190,7 +190,7 @@ def load_cartesian_dataset(xyz_path): # extract energy,geometry pairs #data_regex = "\s*-?\d+\.\d+\s*\n" + xyz_re #data_regex = maybe("\d\d?\n") + "\s*-?\d+\.\d+\s*\n" + xyz_re - data_regex = maybe("\d+\n") + "\s*-?\d+\.\d+\s*\n" + xyz_re + data_regex = maybe(r"\d+\n") + r"\s*-?\d+\.\d+\s*\n" + xyz_re datablock = re.findall(data_regex, data) for i in range(len(datablock)): datablock[i] = list(filter(None, datablock[i].split('\n'))) @@ -206,7 +206,7 @@ def load_cartesian_dataset(xyz_path): geoms = datablock # find atom labels sample = geoms[0] - atom_labels = [re.findall('\w+', s)[0] for s in sample] + atom_labels = [re.findall(r'\w+', s)[0] for s in sample] natoms = len(atom_labels) # convert atom labels to standard order (most common element first, alphabetical tiebreaker) sorted_atom_counts = collections.Counter(atom_labels).most_common() @@ -227,12 +227,16 @@ def load_cartesian_dataset(xyz_path): for g in range(len(geoms)): geoms[g] = [geoms[g][i] for i in p] - # write new xyz file with standard order - #with open('std_' + xyz_path, 'w+') as f: - # for i in range(len(energies)): - # f.write(energies[i] +'\n') - # for j in range(natoms): - # f.write(geoms[i][j] +'\n') + + # return standard order and atom counts if no_energy is True + if no_energy: + # with open('std_' + xyz_path, 'w+') as f: + # f.write(str(sorted_atom_counts)) + # f.write('\n') + # for i in range(natoms): + # # f.write(geoms[i][j] +'\n') + # f.write(str(geoms[0][i])) + return sorted_atom_labels, geoms # remove everything from XYZs except floats and convert to numpy arrays for i,geom in enumerate(geoms): @@ -263,6 +267,53 @@ def load_cartesian_dataset(xyz_path): DF.to_csv(finalpath + '_interatomics.dat',index=False, float_format='%12.10f') return DF +def remove_atom_labels(geom): + for i in range(len(geom)): + tmp = re.sub(atom_symbol, '', geom[i]).lstrip() + geom[i] = [float(g) for g in tmp.split()] + return geom + +def write_grad_input(sorted_atoms): + input_string = '' + distance_list = '' + atom_grad_label = '' + new_atom_label = sorted_atoms + + input_string += 'import torch\nimport peslearn\n\n' + input_string += 'def gradient_prediction(nn, params, model, Xscaler, yscaler, coord):\n' + input_string += '\t"""\n\tdescription here\n\toutput in hartrees\n\t"""\n' + + for i in range(len(sorted_atoms)): + new_atom_label[i] = str(sorted_atoms[i]) + str(i) + input_string += '\t' + new_atom_label[i] + ' = torch.tensor([coord[{}]], requires_grad=True, dtype=torch.float32)\n'.format(str(i)) + + input_string += '\n' + + for i in range(len(sorted_atoms)): + for j in range(i): + input_string += '\t' + str(sorted_atoms[j]) + str(sorted_atoms[i]) + ' = torch.cdist({}, {})\n'.format(sorted_atoms[j], sorted_atoms[i]) + if i == len(sorted_atoms) - 1: + if j == i - 1: + distance_list += str(sorted_atoms[j]) + str(sorted_atoms[i]) + else: + distance_list += str(sorted_atoms[j]) + str(sorted_atoms[i]) + ', ' + else: + distance_list += str(sorted_atoms[j]) + str(sorted_atoms[i]) + ', ' + input_string += '\n' + input_string += '\tinput_tensor = torch.cat(({}), dim=1)\n\n'.format(distance_list) + input_string += '\tpreprocessed_input = nn.torch_transform_new_X(input_tensor, params, Xscaler)\n' + input_string += '\tmodel_output = model(preprocessed_input)\n' + input_string += '\ttransf_output = nn.alt_inverse_new_y(model_output, params, yscaler=yscaler)\n' + input_string += '\ttransf_output.sum().backward()\n\n' + for k in range(len(new_atom_label)): + atom_grad_label += '{}grad'.format(new_atom_label[k]) + input_string += '\t{}grad = -{}.grad\n'.format(new_atom_label[k], new_atom_label[k]) + if k != len(new_atom_label) - 1: + atom_grad_label += ', ' + input_string += '\tgrad_pred = torch.cat(({}), dim=0)\n\n'.format(atom_grad_label) + input_string += '\treturn grad_pred' + with open('grad_func.py', 'w+') as f: + f.write(input_string) diff --git a/peslearn/utils/parsing_helper.py b/peslearn/utils/parsing_helper.py index f67f7a29..9a537a13 100644 --- a/peslearn/utils/parsing_helper.py +++ b/peslearn/utils/parsing_helper.py @@ -22,7 +22,7 @@ def extract_energy(input_obj, output_obj): energy = output_obj.extract_energy_with_regex(input_obj.keywords['energy_regex']) return energy else: - raise Exception("\n energy_regex value not assigned in input. Please add a regular expression which captures the energy value, e.g. energy_regex = 'RHF Final Energy: \s+(-\d+\.\d+)'") + raise Exception("\n energy_regex value not assigned in input. Please add a regular expression which captures the energy value, see docs for more info.") if input_obj.keywords['energy'] == 'schema': def extract_energy(input_obj, output_obj): diff --git a/peslearn/utils/regex.py b/peslearn/utils/regex.py index 711ac065..342dddfa 100644 --- a/peslearn/utils/regex.py +++ b/peslearn/utils/regex.py @@ -26,7 +26,7 @@ def two_or_more(string): # save some common regex features as human readable variables letter = r'[a-zA-Z]' # floating point number with optional scientific notation -double = r'-?\d+\.\d+' + maybe('[E,e]' + '[\-,\+]?\d+') +double = r'-?\d+\.' + maybe(r'\d+') + maybe(r'[E,e]' + r'[\-,\+]?\d+') integer = r'\d+' whitespace = r'\s' endline = r'\n' @@ -40,6 +40,9 @@ def two_or_more(string): # an xyz style geometry block of any size xyz_block_regex = two_or_more(xyz_line_regex) +# a regex for the atom symbol only in an xyz style geometry line +atom_symbol = r'[ \t]*' + letter + maybe(letter) + # define generalized compact internal coordinates regex identifier # e.g. # O diff --git a/pyproject.toml b/pyproject.toml index 7abbc10c..165c585a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,20 +4,20 @@ build-backend = "setuptools.build_meta" [project] name = "peslearn" -version = "1.0.0" +version = "1.0.1" authors = [{name = "Adam Abbott"}, {name = "Ian Beck"},] description = "Automated Construction of Machine Learning Models of Molecular Potential Energy Surfaces." readme = "README.md" requires-python = ">=3.8" license = {text = "BSD-3-Clause"} dependencies = [ - "numpy==1.24.4", + "numpy (>=1.24.4, <2.0.0)", "GPy>=1.9", "scikit-learn>=0.20", "pandas>=2.0", "hyperopt>=0.1.1", "cclib>=1.6", - "torch>=1.0.1", + "torch", "joblib>=1.3.0", "qcelemental>=0.27.1", "qcengine>=0.26.0", diff --git a/setup.py b/setup.py index 1733878d..0aec0c19 100644 --- a/setup.py +++ b/setup.py @@ -3,14 +3,23 @@ if __name__ == "__main__": setuptools.setup( name='peslearn', - version="1.0.0", + version="1.0.1", description='Automated Construction of Machine Learning Models of Molecular Potential Energy Surfaces.', author='Adam Abbott, Ian Beck', url="https://github.com/CCQC/PES-Learn", license='BSD-3C', packages=setuptools.find_packages(), install_requires=[ - 'numpy==1.24.4','GPy>=1.9','scikit-learn>=0.20','pandas>=2.0','hyperopt>=0.1.1','cclib>=1.6', 'torch>=1.0.1', 'joblib>=1.3.0', 'qcelemental>=0.27.1', 'qcengine>=0.26.0' + 'numpy>=1.24.4,<2.0.0', + 'GPy>=1.9', + 'scikit-learn>=0.20', + 'pandas>=2.0', + 'hyperopt>=0.1.1', + 'cclib>=1.6', + 'torch==2.1.2', + 'joblib>=1.3.0', + 'qcelemental>=0.27.1', + 'qcengine>=0.26.0' ], extras_require={ 'docs': [