From e705135c66e79d1b673f01bf9094b07c8f6a5497 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Mon, 17 Jun 2019 21:56:09 +0800 Subject: [PATCH 01/33] New features: interpolation with tabulated potential, frame parameter, python interface, unit tests. merge with the private repo. --- README.md | 202 +++++++++------- data/raw/raw_to_set.sh | 9 + doc/install-tf.1.12.md | 15 -- doc/install-tf.1.8.md | 15 -- examples/lmp/{lammps.in => in.lammps} | 0 source/CMakeLists.txt | 36 ++- source/cmake/Findtensorflow.cmake | 67 +++--- source/lib/include/NNPInter.h | 27 ++- source/lib/src/NNPInter.cc | 229 ++++++++++++++---- source/lib/src/NeighborList.cpp | 45 +++- source/lmp/pair_nnp.cpp | 156 ++++++++++--- source/lmp/pair_nnp.h.in | 6 + source/op/CMakeLists.txt | 124 +++++----- source/op/_soft_min_force_grad.py | 26 +++ source/op/_soft_min_virial_grad.py | 27 +++ source/op/soft_min.cc | 202 ++++++++++++++++ source/op/soft_min_force.cc | 121 ++++++++++ source/op/soft_min_force_grad.cc | 128 ++++++++++ source/op/soft_min_virial.cc | 141 +++++++++++ source/op/soft_min_virial_grad.cc | 151 ++++++++++++ source/op/tab_inter.cc | 324 ++++++++++++++++++++++++++ source/pyproject.toml | 2 + source/scripts/CMakeLists.txt | 6 +- source/scripts/config.py | 191 +++++++++++++++ source/scripts/freeze.py | 18 +- source/setup.py | 40 ++++ source/tests/CMakeLists.txt | 7 + source/tests/common.py | 237 +++++++++++++++++++ source/tests/test_descrpt_nonsmth.py | 202 ++++++++++++++++ source/tests/test_descrpt_smooth.py | 174 ++++++++++++++ source/tests/test_tab_nonsmth.py | 183 +++++++++++++++ source/tests/test_tab_smooth.py | 181 ++++++++++++++ source/train/CMakeLists.txt | 16 +- source/train/Data.py | 81 +++++-- source/train/DataSystem.py | 46 +++- source/train/DeepPot.py | 206 ++++++++++++++++ source/train/Model.py | 132 ++++++++++- source/train/TabInter.py | 51 ++++ source/train/Test.py | 196 +++++++++++++--- source/train/__init__.py | 1 + source/train/__main__.py | 74 ++++++ source/train/test.py | 154 ++---------- source/train/train.py | 23 +- 43 files changed, 3713 insertions(+), 559 deletions(-) rename examples/lmp/{lammps.in => in.lammps} (100%) create mode 100644 source/op/_soft_min_force_grad.py create mode 100644 source/op/_soft_min_virial_grad.py create mode 100644 source/op/soft_min.cc create mode 100644 source/op/soft_min_force.cc create mode 100644 source/op/soft_min_force_grad.cc create mode 100644 source/op/soft_min_virial.cc create mode 100644 source/op/soft_min_virial_grad.cc create mode 100644 source/op/tab_inter.cc create mode 100644 source/pyproject.toml create mode 100644 source/scripts/config.py create mode 100644 source/setup.py create mode 100644 source/tests/CMakeLists.txt create mode 100644 source/tests/common.py create mode 100644 source/tests/test_descrpt_nonsmth.py create mode 100644 source/tests/test_descrpt_smooth.py create mode 100644 source/tests/test_tab_nonsmth.py create mode 100644 source/tests/test_tab_smooth.py create mode 100644 source/train/DeepPot.py create mode 100644 source/train/TabInter.py create mode 100644 source/train/__init__.py create mode 100644 source/train/__main__.py diff --git a/README.md b/README.md index 8bbb53a6d0..0e6d861c61 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,12 @@ - [Deep Potential in a nutshell](#deep-potential-in-a-nutshell) - [Download and install](#download-and-install) - [Easy installation methods](#easy-installation-methods) - - [Install DeePMD-kit from scratch](#install-deepmd-kit-from-scratch) - - [Install tensorflow](#install-tensorflow) - - [Install DeePMD-kit](#install-deepmd-kit) + - [Install the python interaction](#install-the-python-interface) + - [Install the Tensorflow's python interface](#install-the-tensorflows-python-interface) + - [Install the DeePMD-kit's python interface](#install-the-deepmd-kits-python-interface) + - [Install the C++ interaction](#install-the-c-interface) + - [Install the Tensorflow's C++ interface](#install-the-tensorflows-c-interface) + - [Install the DeePMD-kit's C++ interface](#install-the-deepmd-kits-c-interface) - [Install LAMMPS's DeePMD-kit module](#install-lammpss-deepmd-kit-module) - [Build DeePMD-kit with GPU support](#build-deepmd-kit-with-gpu-support) - [Use DeePMD-kit](#use-deepmd-kit) @@ -21,11 +24,11 @@ - [The DeePMD model](#the-deepmd-model) - [The DeepPot-SE model](#the-deeppot-se-model) - [Freeze and test a model](#freeze-and-test-a-model) + - [Model inference](#model-inference) - [Run MD with Lammps](#run-md-with-lammps) - [Include deepmd in the pair style](#include-deepmd-in-the-pair-style) - [Long-range interaction](#long-range-interaction) - [Run path-integral MD with i-PI](#run-path-integral-md-with-i-pi) - - [Run MD with native code](#run-md-with-native-code) - [Troubleshooting](#troubleshooting) # About DeePMD-kit @@ -83,15 +86,32 @@ Please follow our [github](https://github.com/deepmodeling/deepmd-kit) webpage t ## Easy installation methods A docker for installing the DeePMD-kit on CentOS 7 is available [here](https://github.com/frankhan91/deepmd-kit_docker). We are currently working on installation methods using the `conda` package management system and `pip` tools. Hope these will come out soon. -## Install DeePMD-kit from scratch -Installing DeePMD-kit from scratch is lengthy, but do not be panic. Just follow step by step. Wish you good luck.. +## Install the python interface -### Install tensorflow -We tested two tensorflow installation options. You may follow either [tf-1.8](doc/install-tf.1.8.md) or [tf-1.12](doc/install-tf.1.12.md). Click one of the links and follow the instructions therein. Of course, other installation options are not forbidden. +### Install the Tensorflow's python interface +We follow the virtual environment approach to install the tensorflow's Python interface. The full instruction can be found on [the tensorflow's official website](https://www.tensorflow.org/install/pip). Now we assume that the Python interface will be installed to virtual environment directory `$tensorflow_venv` +```bash +virtualenv --system-site-packages -p python3 $tensorflow_venv +source $tensorflow_venv/bin/activate +pip install --upgrade pip +pip install --upgrade tensorflow==1.8.0 +``` +If one needs the GPU support of deepmd-kit, the GPU version of tensorflow should be installed by +```bash +pip install --upgrade tensorflow-gpu==1.8.0 +``` +To verify the installation, +```bash +python -c "import tensorflow as tf; sess=tf.Session(); print(sess.run(tf.reduce_sum(tf.random_normal([1000, 1000]))))" +``` +One should remember to activate the virtual environment every time he/she uses deepmd-kit. -### Install DeePMD-kit -The DeePMD-kit was tested with compiler gcc >= 4.9. +One may also need the follow dependencies that are installed by +```bash +pip install --upgrade cmake scikit-build dpdata +``` +### Install the DeePMD-kit's python interface Firstly clone the DeePMD-kit source code ```bash cd /some/workspace @@ -102,7 +122,50 @@ If one downloads the .zip file from the github, then the default folder of sourc cd deepmd-kit deepmd_source_dir=`pwd` ``` -Then goto the source code directory and make a build directory. +Then goto the source code directory and execute +```bash +cd $deepmd_source_dir/source +python setup.py install +``` +To test the installation, one may execute +```bash +python -m deepmd -h +``` +It will print the help information like +```text +usage: __main__.py [-h] {config,train,freeze,test} ... + +deepmd-kit + +optional arguments: + -h, --help show this help message and exit + +Valid subcommands: + {config,train,freeze,test} + config fast configuration of parameter file for smooth model + train train a model + freeze freeze the model + test test the model +``` + +## Install the C++ interface + +### Install the Tensorflow's C++ interface + +If one does not need to use DeePMD-kit with Lammps or I-Pi, then the python interface does everything and he/she can safely skip this section. + +The C++ interface of DeePMD-kit was tested with compiler gcc >= 4.9. + +Firstly the C++ interface of Tensorflow should be installed. It is noted that the version of Tensorflow C++ interface should be in consistent with that of the python interface. We assume that you have followed our instruction and installed tensorflow python interface 1.8.0, i.e. +```bash +pip install --upgrade tensorflow==1.8.0 +``` +then you may follow [the instruction here](doc/install-tf.1.8.md) to install the corresponding C++ interface. + +Or you have installed Tensorflow's python interface 1.12.0, you may follow [here](doc/install-tf.1.12.md) to install the corresponding C++ interface. + +### Install the DeePMD-kit's C++ interface +Now goto the source code directory of DeePMD-kit and make a build place. ```bash cd $deepmd_source_dir/source mkdir build @@ -110,18 +173,19 @@ cd build ``` I assume you want to install DeePMD-kit into path `$deepmd_root`, then execute cmake ```bash -cmake -DTF_GOOGLE_BIN=true -DTENSORFLOW_ROOT=$tensorflow_root \ --DCMAKE_INSTALL_PREFIX=$deepmd_root .. +cmake -DTENSORFLOW_ROOT=$tensorflow_root -DCMAKE_INSTALL_PREFIX=$deepmd_root .. ``` -If you built the tensorflow's Python interface by gcc>=5.0, then remove the option `-DTF_GOOGLE_BIN=true`. If the cmake has executed successfully, then +If the cmake has executed successfully, then ```bash make make install ``` -If everything works fine, you will have the following executables installed in `$deepmd_root/bin` +If everything works fine, you will have the following executable and libraries installed in `$deepmd_root/bin` and `$deepmd_root/lib` ```bash $ ls $deepmd_root/bin -dp_frz dp_ipi dp_test dp_train +dp_ipi +$ ls $deepmd_root/lib +libdeepmd_ipi.so libdeepmd_op.so libdeepmd.so ``` ### Install LAMMPS's DeePMD-kit module @@ -234,9 +298,9 @@ It generates three sets `set.000`, `set.001` and `set.002`, with each set contai The method of training is explained in our [DeePMD paper][2]. With the source code we provide a small training dataset taken from 400 frames generated by NVT ab-initio water MD trajectory with 300 frames for training and 100 for testing. [An example training parameter file](./examples/train/water.json) is provided. One can try with the training by ```bash $ cd $deepmd_source_dir/examples/train/ -$ $deepmd_root/bin/dp_train water.json +$ python -m deepmd train water.json ``` -`$deepmd_root/bin/dp_train` is the training program, and `water.json` is the `json` format parameter file that controls the training. The components of the `water.json` are +`water.json` is the `json` format parameter file that controls the training. The components of the `water.json` are ```json { "_comment": " model parameters", @@ -292,7 +356,7 @@ The option **`axis_rule`** specifies how to make the axis for the local coordina The option **`fitting_neuron`** (deprecated name **`n_neuron`**) is an integer vector that determines the shape the neural network. The size of the vector is identical to the number of hidden layers of the network. From left to right the members denote the sizes of each hidden layers from input end to the output end, respectively. If two neighboring layers are of the same size, then a [ResNet architecture](https://arxiv.org/abs/1512.03385) is build between them. If the option **`fitting_resnet_dt`** is set `true`, then a timestep is used in the ResNet. -The option **`systems`** provide location of the systems (path to `set.*` and `type.raw`). It is a vector, thus DeePMD-kit allows you to provide multiple systems. DeePMD-kit will train the model with the systems in the vector one by one in a cyclic manner. +The option **`systems`** provide location of the systems (path to `set.*` and `type.raw`). It is a vector, thus DeePMD-kit allows you to provide multiple systems. DeePMD-kit will train the model with the systems in the vector one by one in a cyclic manner. **It is warned that the example water data (in folder `examples/data/water`) is of very limited amount, is provided only for testing purpose, and should not be used to train a productive model.** The option **`batch_size`** specifies the number of frames in each batch. The option **`stop_batch`** specifies the total number of batches will be used in the training. @@ -309,13 +373,22 @@ Since we do not have virial data, the virial prefactors `start_pref_v` and `limi The option **`seed`** specifies the random seed for neural network initialization. If not provided, the `seed` will be initialized with `None`. -During the training, the error of the model is tested every **`disp_freq`** batches with **`numb_test`** frames from the last set in the **`systems`** directory on the fly, and the results are output to **`disp_file`**. +During the training, the error of the model is tested every **`disp_freq`** batches with **`numb_test`** frames from the last set in the **`systems`** directory on the fly, and the results are output to **`disp_file`**. A typical `disp_file` looks like +```bash +# batch l2_tst l2_trn l2_e_tst l2_e_trn l2_f_tst l2_f_trn lr + 0 2.67e+01 2.57e+01 2.21e-01 2.22e-01 8.44e-01 8.12e-01 1.0e-03 + 100 6.14e+00 5.40e+00 3.01e-01 2.99e-01 1.93e-01 1.70e-01 1.0e-03 + 200 5.02e+00 4.49e+00 1.53e-01 1.53e-01 1.58e-01 1.42e-01 1.0e-03 + 300 4.36e+00 3.71e+00 7.32e-02 7.27e-02 1.38e-01 1.17e-01 1.0e-03 + 400 4.04e+00 3.29e+00 3.16e-02 3.22e-02 1.28e-01 1.04e-01 1.0e-03 +``` +The first column displays the number of batches. The second and third columns display the loss function evaluated by `numb_test` frames randomly chosen from the test set and that evaluated by the current training batch, respectively. The fourth and fifth columns display the RMS energy error (normalized by number of atoms) evaluated by `numb_test` frames randomly chosen from the test set and that evaluated by the current training batch, respectively. The sixth and seventh columns display the RMS force error (component-wise) evaluated by `numb_test` frames randomly chosen from the test set and that evaluated by the current training batch, respectively. The last column displays the current learning rate. Checkpoints will be written to files with prefix **`save_ckpt`** every **`save_freq`** batches. If **`restart`** is set to `true`, then the training will start from the checkpoint named **`load_ckpt`**, rather than from scratch. -Several command line options can be passed to `dp_train`, which can be checked with +Several command line options can be passed to `python -m deepmd train`, which can be checked with ```bash -$ $deepmd_root/bin/dp_train --help +$ python -m deepmd train --help ``` An explanation will be provided ``` @@ -340,7 +413,7 @@ The keys `intra_op_parallelism_threads` and `inter_op_parallelism_threads` are T The smooth version of DeePMD, or the [DeepPot-SE model][3], can also be trained by DeePMD-kit. [An example training parameter file](./examples/train/water_smth.json) is provided. One can try with the training by ```bash $ cd $deepmd_source_dir/examples/train/ -$ $deepmd_root/bin/dp_train water_smth.json +$ python -m deepmd train water_smth.json ``` The difference between the standard and smooth DeePMD models lies in the model parameters: ```json @@ -369,18 +442,19 @@ The **`filter_neuron`** provides the size of the filter network (also called loc ## Freeze and test a model The trained neural network is extracted from a checkpoint and dumped into a database. This process is called "freezing" a model. The idea and part of our code are from [Morgan](https://blog.metaflow.fr/tensorflow-how-to-freeze-a-model-and-serve-it-with-a-python-api-d4f3596b3adc). To freeze a model, typically one does ```bash -$ $deepmd_root/bin/dp_frz -o graph.pb +$ python -m deepmd freeze -o graph.pb ``` in the folder where the model is trained. The output database is called `graph.pb`. -The frozen model can be used in many ways. The most straightforward test can be performed using `dp_test`. Several command line options can be passed to `dp_test`, which can be checked with +The frozen model can be used in many ways. The most straightforward test can be performed using `python -m deepmd test`. Several command line options can be passed to `python -m deepmd test`, which can be checked with ```bash -$ $deepmd_root/bin/dp_test --help +$ python -m deepmd test --help ``` An explanation will be provided ``` -usage: dp_test [-h] [-m MODEL] [-s SYSTEM] [-S SET_PREFIX] [-n NUMB_TEST] - [-d DETAIL_FILE] +usage: __main__.py test [-h] [-m MODEL] [-s SYSTEM] [-S SET_PREFIX] + [-n NUMB_TEST] [-r RAND_SEED] [--shuffle-test] + [-d DETAIL_FILE] optional arguments: -h, --help show this help message and exit @@ -392,11 +466,27 @@ optional arguments: The set prefix -n NUMB_TEST, --numb-test NUMB_TEST The number of data for test + -r RAND_SEED, --rand-seed RAND_SEED + The random seed + --shuffle-test Shuffle test data -d DETAIL_FILE, --detail-file DETAIL_FILE The file containing details of energy force and virial accuracy ``` -The files `dp_frz` and `dp_test` may also serve as a python template for further analyses and more user-specific applications. + +## Model inference +One may use the python interface of DeePMD-kit for model inference, an example is given as follows +```python +import deepmd.DeepPot as DP +import numpy as np +dp = DP('graph.pb') +coord = np.array([[1,0,0], [0,0,1.5], [1,0,3]]).reshape([1, -1]) +cell = np.diag(10 * np.ones(3)).reshape([1, -1]) +atype = [1,0,1] +e, f, v = dp.eval(coord, cell, atype) +``` +where `e`, `f` and `v` are predicted energy, force and virial of the system, respectively. + ## Run MD with LAMMPS ### Include deepmd in the pair style @@ -447,60 +537,6 @@ The option **`graph_file`** provides the file name of the frozen model. The `dp_ipi` gets the atom names from an [XYZ file](https://en.wikipedia.org/wiki/XYZ_file_format) provided by **`coord_file`** (meanwhile ignores all coordinates in it), and translates the names to atom types by rules provided by **`atom_type`**. -## Run MD with native code -DeePMD-kit provides a simple MD implementation that runs under either NVE or NVT ensemble. One needs to provide the following input files -```bash -$ ls -conf.gro graph.pb water.json -``` -`conf.gro` is the file that provides the initial coordinates and/or velocities of all atoms in the system. It is of Gromacs `gro` format. Details of this format can be find in [this website](http://manual.gromacs.org/current/online/gro.html). It should be notice that the length unit of the `gro` format is **nm** rather than A. - -`graph.pb` is the frozen model. - -`water.json` is the parameter file that specifies how the MD runs. [An example parameter file](./examples/md/water.json) for water NVT simulation is provided. -```json -{ - "conf_file": "conf.gro", - "conf_format": "gro", - "graph_file": "graph.pb", - "nsteps": 500000, - "dt": 5e-4, - "ener_freq": 20, - "ener_file": "energy.out", - "xtc_freq": 20, - "xtc_file": "traj.xtc", - "trr_freq": 20, - "trr_file": "traj.trr", - "print_force": false, - "T": 300, - "tau_T": 0.1, - "rand_seed": 2017, - "atom_type" : { - "OW": 0, - "HW1": 1, - "HW2": 1 - }, - "atom_mass" : { - "OW": 16, - "HW1": 1, - "HW2": 1 - } -} -``` -The options **`conf_file`**, **`conf_format`** and **`graph_file`** are self-explanatory. It should be noticed, again, the length unit is nm in the `gro` format file. - -The option **`nsteps`** specifies the number of time steps of the MD simulation. The option **`dt`** specifies the timestep of the simulation. - -The options **`ener_file`** and **`ener_freq`** specify the energy output file and frequency. - -The options **`xtc_file`**, **`xtc_freq`**, **`trr_file`** and **`trr_freq`** are similar options that specify the output files and frequencies of the xtc and trr trajectory, respectively. When the frequencies are set to 0, the corresponding file will not be output. The instructions of the xtc and trr formats can be found in [xtc manual](http://manual.gromacs.org/online/xtc.html) and [trr manual](http://manual.gromacs.org/online/trr.html). It is noticed that the length unit in the xtc and trr files is **nm**. - -If the option **`print_force`** is set to `true`, then the atomic force will be output. - -The option **`T`** specifies the temperature of the simulation, and the option **`tau_T`** specifies the timescale of the thermostat. We implement the Langevin thermostat for the NVT simulation. **`rand_seed`** set the random seed of the random generator in the thermostat. - -The **`atom_type`** set the type for the atoms in the system. The names of the atoms are those provided in the `conf_file` file. The **`atom_mass`** set the mass for the atoms. Again, the name of the atoms are those provided in the `conf_file`. - # Troubleshooting In consequence of various differences of computers or systems, problems may occur. Some common circumstances are listed as follows. If other unexpected problems occur, you're welcome to contact us for help. diff --git a/data/raw/raw_to_set.sh b/data/raw/raw_to_set.sh index 863a59c7c7..58f2ab4ce5 100755 --- a/data/raw/raw_to_set.sh +++ b/data/raw/raw_to_set.sh @@ -16,6 +16,7 @@ test -f energy.raw && split energy.raw -l $nline_per_set -d -a 3 energy.raw test -f force.raw && split force.raw -l $nline_per_set -d -a 3 force.raw test -f virial.raw && split virial.raw -l $nline_per_set -d -a 3 virial.raw test -f atom_ener.raw && split atom_ener.raw -l $nline_per_set -d -a 3 atom_ener.raw +test -f fparam.raw && split fparam.raw -l $nline_per_set -d -a 3 fparam.raw nset=`ls | grep box.raw[0-9] | wc -l` nset_1=$(($nset-1)) @@ -32,6 +33,7 @@ do test -f force.raw$pi && mv force.raw$pi set.$pi/force.raw test -f virial.raw$pi && mv virial.raw$pi set.$pi/virial.raw test -f atom_ener.raw$pi && mv atom_ener.raw$pi set.$pi/atom_ener.raw + test -f fparam.raw$pi && mv fparam.raw$pi set.$pi/fparam.raw cd set.$pi python -c 'import numpy as np; data = np.loadtxt("box.raw" ); data = data.astype (np.float32); np.save ("box", data)' @@ -63,6 +65,13 @@ if os.path.isfile("atom_ener.raw"): data = np.loadtxt("atom_ener.raw"); data = data.astype (np.float32); np.save ("atom_ener", data) +' + python -c \ +'import numpy as np; import os.path; +if os.path.isfile("fparam.raw"): + data = np.loadtxt("fparam.raw"); + data = data.astype (np.float32); + np.save ("fparam", data) ' rm *.raw cd ../ diff --git a/doc/install-tf.1.12.md b/doc/install-tf.1.12.md index 42a297cee8..b54582d1a7 100644 --- a/doc/install-tf.1.12.md +++ b/doc/install-tf.1.12.md @@ -1,18 +1,3 @@ -# Install tensorflow's Python interface -We follow the virtual environment approach to install the tensorflow's Python interface. The full instruction can be found on [the tensorflow's official website](https://www.tensorflow.org/install/pip). Now we assume that the Python interface will be installed to virtual environment directory `$tensorflow_venv` -```bash -virtualenv --system-site-packages -p python3 $tensorflow_venv -source $tensorflow_venv/bin/activate -pip install --upgrade pip -pip install --upgrade tensorflow==1.12.0 -``` -To verify the installation, -```bash -python -c "import tensorflow as tf; tf.enable_eager_execution(); print(tf.reduce_sum(tf.random_normal([1000, 1000])))" -``` - -One should remember to activate the virtual environment every time he/she runs deepmd training program `dp_train`. - # Install tensorflow's C++ interface The tensorflow's C++ interface will be compiled from the source code. Firstly one installs bazel. It is highly recommended that the bazel version 0.15.0 is used. A full instruction of bazel installation can be found [here](https://docs.bazel.build/versions/master/install.html). ```bash diff --git a/doc/install-tf.1.8.md b/doc/install-tf.1.8.md index fd4ef0acca..8c7902f044 100644 --- a/doc/install-tf.1.8.md +++ b/doc/install-tf.1.8.md @@ -1,18 +1,3 @@ -# Install tensorflow's Python interface -We follow the virtual environment approach to install the tensorflow's Python interface. The full instruction can be found on [the tensorflow's official website](https://www.tensorflow.org/install/pip). Now we assume that the Python interface will be installed to virtual environment directory `$tensorflow_venv` -```bash -virtualenv --system-site-packages -p python3 $tensorflow_venv -source $tensorflow_venv/bin/activate -pip install --upgrade pip -pip install --upgrade tensorflow==1.8.0 -``` -To verify the installation, -```bash -python -c "import tensorflow as tf; sess=tf.Session(); print(sess.run(tf.reduce_sum(tf.random_normal([1000, 1000]))))" -``` - -One should remember to activate the virtual environment every time he/she runs deepmd training program `dp_train`. - # Install tensorflow's C++ interface The tensorflow's C++ interface will be compiled from the source code. Firstly one installs bazel. It is highly recommended that the bazel version 0.10.0 is used. A full instruction of bazel installation can be found [here](https://docs.bazel.build/versions/master/install.html). ```bash diff --git a/examples/lmp/lammps.in b/examples/lmp/in.lammps similarity index 100% rename from examples/lmp/lammps.in rename to examples/lmp/in.lammps diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 1bdb10ba62..21e3598da2 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,6 +1,14 @@ cmake_minimum_required(VERSION 3.0) project(DeePMD) +# build cpp or python interfaces +if (NOT DEFINED BUILD_CPP_IF) + set(BUILD_CPP_IF TRUE) +endif (NOT DEFINED BUILD_CPP_IF) +if (NOT DEFINED BUILD_PY_IF) + set(BUILD_PY_IF FALSE) +endif (NOT DEFINED BUILD_PY_IF) + find_package(Git) if(GIT_FOUND) execute_process( @@ -29,9 +37,6 @@ if(GIT_FOUND) ) endif(GIT_FOUND) -# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0") -# set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0") - # global defines list (APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/) list (APPEND CMAKE_CXX_FLAGS "-std=c++11 -Wno-ignored-attributes") @@ -85,19 +90,26 @@ include_directories(${DeePMD_INCLUDE_DIRS}) include_directories(${TensorFlow_INCLUDE_DIRS}) # define names of libs -set (LIB_DEEPMD "deepmd") -set (LIB_DEEPMD_OP "deepmd_op") -set (LIB_DEEPMD_NATIVE "deepmd_native_md") -set (LIB_DEEPMD_IPI "deepmd_ipi") +if (BUILD_CPP_IF) + set (LIB_DEEPMD "deepmd") + set (LIB_DEEPMD_OP "deepmd_op") + set (LIB_DEEPMD_NATIVE "deepmd_native_md") + set (LIB_DEEPMD_IPI "deepmd_ipi") +endif (BUILD_CPP_IF) include_directories(${CMAKE_BINARY_DIR}/lib/) -add_subdirectory (lib/) add_subdirectory (op/) -add_subdirectory (train/) +if (BUILD_PY_IF) + add_subdirectory (train/) + add_subdirectory (scripts/) + add_subdirectory (tests/) +endif (BUILD_PY_IF) +if (BUILD_CPP_IF) + add_subdirectory (lib/) + add_subdirectory (lmp/) # add_subdirectory (md/) -add_subdirectory (ipi/) -add_subdirectory (scripts/) -add_subdirectory (lmp/) + add_subdirectory (ipi/) +endif (BUILD_CPP_IF) # uninstall target configure_file( diff --git a/source/cmake/Findtensorflow.cmake b/source/cmake/Findtensorflow.cmake index 82687c0c76..5c26eeb440 100644 --- a/source/cmake/Findtensorflow.cmake +++ b/source/cmake/Findtensorflow.cmake @@ -1,5 +1,6 @@ # Input: # TENSORFLOW_ROOT +# BUILD_CPP_IF # # Output: # TensorFlow_FOUND @@ -32,27 +33,33 @@ if (NOT TensorFlow_INCLUDE_DIRS AND tensorflow_FIND_REQUIRED) "You can manually set the tensorflow install path by -DTENSORFLOW_ROOT ") endif () -# tensorflow_cc and tensorflow_framework -if (NOT TensorFlow_FIND_COMPONENTS) - set(TensorFlow_FIND_COMPONENTS tensorflow_cc tensorflow_framework) -endif () -# the lib -set (TensorFlow_LIBRARY_PATH "") -foreach (module ${TensorFlow_FIND_COMPONENTS}) - find_library(TensorFlow_LIBRARY_${module} - NAMES ${module} - PATHS ${TensorFlow_search_PATHS} PATH_SUFFIXES lib NO_DEFAULT_PATH - ) - if (TensorFlow_LIBRARY_${module}) - list(APPEND TensorFlow_LIBRARY ${TensorFlow_LIBRARY_${module}}) - get_filename_component(TensorFlow_LIBRARY_PATH_${module} ${TensorFlow_LIBRARY_${module}} PATH) - list(APPEND TensorFlow_LIBRARY_PATH ${TensorFlow_LIBRARY_PATH_${module}}) - elseif (tensorflow_FIND_REQUIRED) - message(FATAL_ERROR - "Not found lib/'${module}' in '${TensorFlow_search_PATHS}' " - "You can manually set the tensorflow install path by -DTENSORFLOW_ROOT ") +if (BUILD_CPP_IF) + message (STATUS "Enabled cpp interface build, looking for tensorflow_cc and tensorflow_framework") + # tensorflow_cc and tensorflow_framework + if (NOT TensorFlow_FIND_COMPONENTS) + set(TensorFlow_FIND_COMPONENTS tensorflow_cc tensorflow_framework) endif () -endforeach () + # the lib + set (TensorFlow_LIBRARY_PATH "") + foreach (module ${TensorFlow_FIND_COMPONENTS}) + find_library(TensorFlow_LIBRARY_${module} + NAMES ${module} + PATHS ${TensorFlow_search_PATHS} PATH_SUFFIXES lib NO_DEFAULT_PATH + ) + if (TensorFlow_LIBRARY_${module}) + list(APPEND TensorFlow_LIBRARY ${TensorFlow_LIBRARY_${module}}) + get_filename_component(TensorFlow_LIBRARY_PATH_${module} ${TensorFlow_LIBRARY_${module}} PATH) + list(APPEND TensorFlow_LIBRARY_PATH ${TensorFlow_LIBRARY_PATH_${module}}) + elseif (tensorflow_FIND_REQUIRED) + message(FATAL_ERROR + "Not found lib/'${module}' in '${TensorFlow_search_PATHS}' " + "You can manually set the tensorflow install path by -DTENSORFLOW_ROOT ") + endif () + endforeach () +else (BUILD_CPP_IF) + message (STATUS "Disabled cpp interface build, looking for tensorflow_framework") +endif (BUILD_CPP_IF) + # tensorflow_framework if (NOT TensorFlowFramework_FIND_COMPONENTS) @@ -76,12 +83,20 @@ foreach (module ${TensorFlowFramework_FIND_COMPONENTS}) endif () endforeach () -# define the output variable -if (TensorFlow_INCLUDE_DIRS AND TensorFlow_LIBRARY AND TensorFlowFramework_LIBRARY) - set(TensorFlow_FOUND TRUE) -else () - set(TensorFlow_FOUND FALSE) -endif () +if (BUILD_CPP_IF) + # define the output variable + if (TensorFlow_INCLUDE_DIRS AND TensorFlow_LIBRARY AND TensorFlowFramework_LIBRARY) + set(TensorFlow_FOUND TRUE) + else () + set(TensorFlow_FOUND FALSE) + endif () +else (BUILD_CPP_IF) + if (TensorFlow_INCLUDE_DIRS AND TensorFlowFramework_LIBRARY) + set(TensorFlow_FOUND TRUE) + else () + set(TensorFlow_FOUND FALSE) + endif () +endif (BUILD_CPP_IF) # print message if (NOT TensorFlow_FIND_QUIETLY) diff --git a/source/lib/include/NNPInter.h b/source/lib/include/NNPInter.h index 162325dd85..ce8145e3ab 100644 --- a/source/lib/include/NNPInter.h +++ b/source/lib/include/NNPInter.h @@ -63,7 +63,8 @@ class NNPInter const vector & coord, const vector & atype, const vector & box, - const int nghost = 0); + const int nghost = 0, + const vector fparam = vector()); void compute (ENERGYTYPE & ener, vector & force, vector & virial, @@ -71,7 +72,8 @@ class NNPInter const vector & atype, const vector & box, const int nghost, - const LammpsNeighborList & lmp_list); + const LammpsNeighborList & lmp_list, + const vector fparam = vector()); void compute (ENERGYTYPE & ener, vector & force, vector & virial, @@ -79,7 +81,8 @@ class NNPInter vector & atom_virial, const vector & coord, const vector & atype, - const vector & box); + const vector & box, + const vector fparam = vector()); void compute (ENERGYTYPE & ener, vector & force, vector & virial, @@ -89,19 +92,23 @@ class NNPInter const vector & atype, const vector & box, const int nghost, - const LammpsNeighborList & lmp_list); + const LammpsNeighborList & lmp_list, + const vector fparam = vector()); VALUETYPE cutoff () const {assert(inited); return rcut;}; int numb_types () const {assert(inited); return ntypes;}; + int dim_fparam () const {assert(inited); return dfparam;}; private: Session* session; int num_intra_nthreads, num_inter_nthreads; GraphDef graph_def; bool inited; + template VT get_scalar(const string & name) const; VALUETYPE get_rcut () const; int get_ntypes () const; VALUETYPE rcut; VALUETYPE cell_size; int ntypes; + int dfparam; }; class NNPInterModelDevi @@ -117,7 +124,8 @@ class NNPInterModelDevi vector & model_devi, const vector & coord, const vector & atype, - const vector & box); + const vector & box, + const vector fparam = vector()); void compute (vector & all_ener, vector > & all_force, vector > & all_virial, @@ -125,7 +133,8 @@ class NNPInterModelDevi const vector & atype, const vector & box, const int nghost, - const LammpsNeighborList & lmp_list); + const LammpsNeighborList & lmp_list, + const vector fparam = vector()); void compute (vector & all_ener, vector > & all_force, vector > & all_virial, @@ -135,9 +144,11 @@ class NNPInterModelDevi const vector & atype, const vector & box, const int nghost, - const LammpsNeighborList & lmp_list); + const LammpsNeighborList & lmp_list, + const vector fparam = vector()); VALUETYPE cutoff () const {assert(inited); return rcut;}; int numb_types () const {assert(inited); return ntypes;}; + int dim_fparam () const {assert(inited); return dfparam;}; #ifndef HIGH_PREC void compute_avg (ENERGYTYPE & dener, const vector & all_energy); @@ -158,11 +169,13 @@ class NNPInterModelDevi int num_intra_nthreads, num_inter_nthreads; vector graph_defs; bool inited; + template VT get_scalar(const string name) const; VALUETYPE get_rcut () const; int get_ntypes () const; VALUETYPE rcut; VALUETYPE cell_size; int ntypes; + int dfparam; }; diff --git a/source/lib/src/NNPInter.cc b/source/lib/src/NNPInter.cc index 51d5fb1cd4..2d634d1512 100644 --- a/source/lib/src/NNPInter.cc +++ b/source/lib/src/NNPInter.cc @@ -1,6 +1,7 @@ #include "NNPInter.h" #include "NNPAtomMap.h" #include "SimulationRegion.h" +#include static void @@ -58,7 +59,8 @@ make_input_tensors (std::vector> & input_tensors, const int & ntypes, const vector & datype_, const vector & dbox, - const VALUETYPE & cell_size, + const VALUETYPE & cell_size, + const vector fparam_, const NNPAtomMap&nnpmap, const int nghost = 0) { @@ -116,27 +118,29 @@ make_input_tensors (std::vector> & input_tensors, } TensorShape natoms_shape ; natoms_shape.AddDim (2 + ntypes); + TensorShape fparam_shape ; + fparam_shape.AddDim (nframes); + fparam_shape.AddDim (fparam_.size()); #ifdef HIGH_PREC Tensor coord_tensor (DT_DOUBLE, coord_shape); - Tensor type_tensor (DT_INT32, type_shape); Tensor box_tensor (DT_DOUBLE, box_shape); - Tensor mesh_tensor (DT_INT32, mesh_shape); - Tensor natoms_tensor (DT_INT32, natoms_shape); + Tensor fparam_tensor (DT_DOUBLE, fparam_shape); #else Tensor coord_tensor (DT_FLOAT, coord_shape); - Tensor type_tensor (DT_INT32, type_shape); Tensor box_tensor (DT_FLOAT, box_shape); + Tensor fparam_tensor (DT_FLOAT, fparam_shape); +#endif + Tensor type_tensor (DT_INT32, type_shape); Tensor mesh_tensor (DT_INT32, mesh_shape); Tensor natoms_tensor (DT_INT32, natoms_shape); -#endif auto coord = coord_tensor.matrix (); auto type = type_tensor.matrix (); auto box = box_tensor.matrix (); auto mesh = mesh_tensor.flat (); - auto natoms = natoms_tensor.flat (); - + auto natoms = natoms_tensor.flat (); + auto fparam = fparam_tensor.matrix (); vector dcoord (dcoord_); nnpmap.forward (dcoord.begin(), dcoord_.begin(), 3); @@ -151,6 +155,9 @@ make_input_tensors (std::vector> & input_tensors, for (int jj = 0; jj < nall; ++jj){ type(ii, jj) = datype[jj]; } + for (int jj = 0; jj < fparam_.size(); ++jj){ + fparam(ii, jj) = fparam_[jj]; + } } mesh (1-1) = 0; mesh (2-1) = 0; @@ -170,13 +177,25 @@ make_input_tensors (std::vector> & input_tensors, natoms (1) = nall; for (int ii = 0; ii < ntypes; ++ii) natoms(ii+2) = type_count[ii]; - input_tensors = { - {"t_coord", coord_tensor}, - {"t_type", type_tensor}, - {"t_box", box_tensor}, - {"t_mesh", mesh_tensor}, - {"t_natoms", natoms_tensor}, - }; + if (fparam_.size() == 0) { + input_tensors = { + {"t_coord", coord_tensor}, + {"t_type", type_tensor}, + {"t_box", box_tensor}, + {"t_mesh", mesh_tensor}, + {"t_natoms", natoms_tensor}, + }; + } + else { + input_tensors = { + {"t_coord", coord_tensor}, + {"t_type", type_tensor}, + {"t_box", box_tensor}, + {"t_mesh", mesh_tensor}, + {"t_natoms", natoms_tensor}, + {"t_fparam", fparam_tensor}, + }; + } return nloc; } @@ -188,6 +207,7 @@ make_input_tensors (std::vector> & input_tensors, const vector & datype_, const vector & dbox, InternalNeighborList & dlist, + const vector fparam_, const NNPAtomMap&nnpmap, const int nghost) { @@ -218,26 +238,29 @@ make_input_tensors (std::vector> & input_tensors, mesh_shape.AddDim (16); TensorShape natoms_shape ; natoms_shape.AddDim (2 + ntypes); + TensorShape fparam_shape ; + fparam_shape.AddDim (nframes); + fparam_shape.AddDim (fparam_.size()); #ifdef HIGH_PREC Tensor coord_tensor (DT_DOUBLE, coord_shape); - Tensor type_tensor (DT_INT32, type_shape); Tensor box_tensor (DT_DOUBLE, box_shape); - Tensor mesh_tensor (DT_INT32, mesh_shape); - Tensor natoms_tensor (DT_INT32, natoms_shape); + Tensor fparam_tensor (DT_DOUBLE, fparam_shape); #else Tensor coord_tensor (DT_FLOAT, coord_shape); - Tensor type_tensor (DT_INT32, type_shape); Tensor box_tensor (DT_FLOAT, box_shape); + Tensor fparam_tensor (DT_FLOAT, fparam_shape); +#endif + Tensor type_tensor (DT_INT32, type_shape); Tensor mesh_tensor (DT_INT32, mesh_shape); Tensor natoms_tensor (DT_INT32, natoms_shape); -#endif auto coord = coord_tensor.matrix (); auto type = type_tensor.matrix (); auto box = box_tensor.matrix (); auto mesh = mesh_tensor.flat (); auto natoms = natoms_tensor.flat (); + auto fparam = fparam_tensor.matrix (); vector dcoord (dcoord_); nnpmap.forward (dcoord.begin(), dcoord_.begin(), 3); @@ -252,6 +275,9 @@ make_input_tensors (std::vector> & input_tensors, for (int jj = 0; jj < nall; ++jj){ type(ii, jj) = datype[jj]; } + for (int jj = 0; jj < fparam_.size(); ++jj){ + fparam(ii, jj) = fparam_[jj]; + } } for (int ii = 0; ii < 16; ++ii) mesh(ii) = 0; @@ -271,13 +297,25 @@ make_input_tensors (std::vector> & input_tensors, natoms (1) = nall; for (int ii = 0; ii < ntypes; ++ii) natoms(ii+2) = type_count[ii]; - input_tensors = { - {"t_coord", coord_tensor}, - {"t_type", type_tensor}, - {"t_box", box_tensor}, - {"t_mesh", mesh_tensor}, - {"t_natoms", natoms_tensor}, - }; + if (fparam_.size() == 0) { + input_tensors = { + {"t_coord", coord_tensor}, + {"t_type", type_tensor}, + {"t_box", box_tensor}, + {"t_mesh", mesh_tensor}, + {"t_natoms", natoms_tensor}, + }; + } + else { + input_tensors = { + {"t_coord", coord_tensor}, + {"t_type", type_tensor}, + {"t_box", box_tensor}, + {"t_mesh", mesh_tensor}, + {"t_natoms", natoms_tensor}, + {"t_fparam", fparam_tensor}, + }; + } return nloc; } @@ -448,9 +486,16 @@ NNPInter (const string & model) checkStatus (NewSession(options, &session)); checkStatus (ReadBinaryProto(Env::Default(), model, &graph_def)); checkStatus (session->Create(graph_def)); - rcut = get_rcut(); + rcut = get_scalar("t_rcut"); cell_size = rcut; - ntypes = get_ntypes(); + ntypes = get_scalar("t_ntypes"); + dfparam = get_scalar("t_dfparam"); + assert(rcut == get_rcut()); + assert(ntypes == get_ntypes()); + if (dfparam < 0) dfparam = 0; + // rcut = get_rcut(); + // ntypes = get_ntypes(); + // dfparam = get_dfparam(); inited = true; } @@ -465,9 +510,17 @@ init (const string & model) checkStatus (NewSession(options, &session)); checkStatus (ReadBinaryProto(Env::Default(), model, &graph_def)); checkStatus (session->Create(graph_def)); - rcut = get_rcut(); + rcut = get_scalar("t_rcut"); cell_size = rcut; - ntypes = get_ntypes(); + ntypes = get_scalar("t_ntypes"); + dfparam = get_scalar("t_dfparam"); + assert(rcut == get_rcut()); + assert(ntypes == get_ntypes()); + if (dfparam < 0) dfparam = 0; + // rcut = get_rcut(); + // cell_size = rcut; + // ntypes = get_ntypes(); + // dfparam = get_dfparam(); inited = true; } @@ -487,6 +540,20 @@ print_summary(const string &pre) const cout << pre << "set tf inter_op_parallelism_threads: " << num_inter_nthreads << endl; } +template +VT +NNPInter:: +get_scalar (const string & name) const +{ + std::vector output_tensors; + checkStatus (session->Run(std::vector> ({}), + {name.c_str()}, + {}, + &output_tensors)); + Tensor output_rc = output_tensors[0]; + auto orc = output_rc.flat (); + return orc(0); +} VALUETYPE NNPInter:: @@ -524,15 +591,19 @@ compute (ENERGYTYPE & dener, const vector & dcoord_, const vector & datype_, const vector & dbox, - const int nghost) + const int nghost, + const vector fparam) { int nall = dcoord_.size() / 3; int nloc = nall - nghost; NNPAtomMap nnpmap (datype_.begin(), datype_.begin() + nloc); assert (nloc == nnpmap.get_type().size()); + if (fparam.size() != dfparam) { + throw std::runtime_error("the dim of frame parameter provided is not consistent with what the model uses"); + } std::vector> input_tensors; - int ret = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, cell_size, nnpmap, nghost); + int ret = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, cell_size, fparam, nnpmap, nghost); assert (ret == nloc); run_model (dener, dforce_, dvirial, session, input_tensors, nnpmap, nghost); @@ -547,19 +618,23 @@ compute (ENERGYTYPE & dener, const vector & datype_, const vector & dbox, const int nghost, - const LammpsNeighborList & lmp_list) + const LammpsNeighborList & lmp_list, + const vector fparam) { int nall = dcoord_.size() / 3; int nloc = nall - nghost; NNPAtomMap nnpmap (datype_.begin(), datype_.begin() + nloc); assert (nloc == nnpmap.get_type().size()); + if (fparam.size() != dfparam) { + throw std::runtime_error("the dim of frame parameter provided is not consistent with what the model uses"); + } InternalNeighborList nlist; convert_nlist_lmp_internal (nlist, lmp_list); shuffle_nlist (nlist, nnpmap); std::vector> input_tensors; - int ret = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, nlist, nnpmap, nghost); + int ret = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, nlist, fparam, nnpmap, nghost); assert (nloc == ret); run_model (dener, dforce_, dvirial, session, input_tensors, nnpmap, nghost); @@ -575,12 +650,16 @@ compute (ENERGYTYPE & dener, vector & datom_virial_, const vector & dcoord_, const vector & datype_, - const vector & dbox) + const vector & dbox, + const vector fparam) { NNPAtomMap nnpmap (datype_.begin(), datype_.end()); + if (fparam.size() != dfparam) { + throw std::runtime_error("the dim of frame parameter provided is not consistent with what the model uses"); + } std::vector> input_tensors; - int nloc = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, cell_size, nnpmap); + int nloc = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, cell_size, fparam, nnpmap); run_model (dener, dforce_, dvirial, datom_energy_, datom_virial_, session, input_tensors, nnpmap); } @@ -598,19 +677,23 @@ compute (ENERGYTYPE & dener, const vector & datype_, const vector & dbox, const int nghost, - const LammpsNeighborList & lmp_list) + const LammpsNeighborList & lmp_list, + const vector fparam) { int nall = dcoord_.size() / 3; int nloc = nall - nghost; NNPAtomMap nnpmap (datype_.begin(), datype_.begin() + nloc); assert (nloc == nnpmap.get_type().size()); + if (fparam.size() != dfparam) { + throw std::runtime_error("the dim of frame parameter provided is not consistent with what the model uses"); + } InternalNeighborList nlist; convert_nlist_lmp_internal (nlist, lmp_list); shuffle_nlist (nlist, nnpmap); std::vector> input_tensors; - int ret = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, nlist, nnpmap, nghost); + int ret = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, nlist, fparam, nnpmap, nghost); assert (nloc == ret); run_model (dener, dforce_, dvirial, datom_energy_, datom_virial_, session, input_tensors, nnpmap, nghost); @@ -642,9 +725,14 @@ NNPInterModelDevi (const vector & models) checkStatus (ReadBinaryProto(Env::Default(), models[ii], &graph_defs[ii])); checkStatus (sessions[ii]->Create(graph_defs[ii])); } - rcut = get_rcut(); + rcut = get_scalar("t_rcut"); cell_size = rcut; - ntypes = get_ntypes(); + ntypes = get_scalar("t_ntypes"); + dfparam = get_scalar("t_dfparam"); + if (dfparam < 0) dfparam = 0; + // rcut = get_rcut(); + // cell_size = rcut; + // ntypes = get_ntypes(); inited = true; } @@ -664,12 +752,42 @@ init (const vector & models) checkStatus (ReadBinaryProto(Env::Default(), models[ii], &graph_defs[ii])); checkStatus (sessions[ii]->Create(graph_defs[ii])); } - rcut = get_rcut(); + rcut = get_scalar("t_rcut"); cell_size = rcut; - ntypes = get_ntypes(); + ntypes = get_scalar("t_ntypes"); + dfparam = get_scalar("t_dfparam"); + if (dfparam < 0) dfparam = 0; + // rcut = get_rcut(); + // cell_size = rcut; + // ntypes = get_ntypes(); inited = true; } +template +VT +NNPInterModelDevi:: +get_scalar(const string name) const +{ + VT myrcut = 0; + for (unsigned ii = 0; ii < numb_models; ++ii){ + std::vector output_tensors; + checkStatus (sessions[ii]->Run(std::vector> ({}), + {name.c_str()}, + {}, + &output_tensors)); + Tensor output_rc = output_tensors[0]; + auto orc = output_rc.flat (); + if (ii == 0){ + myrcut = orc(0); + } + else { + assert (myrcut == orc(0)); + } + } + return myrcut; +} + + VALUETYPE NNPInterModelDevi:: get_rcut () const @@ -716,6 +834,7 @@ get_ntypes () const return myntypes; } + void NNPInterModelDevi:: compute (ENERGYTYPE & dener, @@ -724,14 +843,18 @@ compute (ENERGYTYPE & dener, vector & model_devi, const vector & dcoord_, const vector & datype_, - const vector & dbox) + const vector & dbox, + const vector fparam) { if (numb_models == 0) return; + if (fparam.size() != dfparam) { + throw std::runtime_error("the dim of frame parameter provided is not consistent with what the model uses"); + } NNPAtomMap nnpmap (datype_.begin(), datype_.end()); std::vector> input_tensors; - int nloc = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, cell_size, nnpmap); + int nloc = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, cell_size, fparam, nnpmap); vector all_energy (numb_models); vector > all_force (numb_models); @@ -769,9 +892,13 @@ compute (vector & all_energy, const vector & datype_, const vector & dbox, const int nghost, - const LammpsNeighborList & lmp_list) + const LammpsNeighborList & lmp_list, + const vector fparam) { if (numb_models == 0) return; + if (fparam.size() != dfparam) { + throw std::runtime_error("the dim of frame parameter provided is not consistent with what the model uses"); + } int nall = dcoord_.size() / 3; int nloc = nall - nghost; @@ -783,7 +910,7 @@ compute (vector & all_energy, shuffle_nlist (nlist, nnpmap); std::vector> input_tensors; - int ret = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, nlist, nnpmap, nghost); + int ret = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, nlist, fparam, nnpmap, nghost); assert (nloc == ret); all_energy.resize (numb_models); @@ -806,9 +933,13 @@ compute (vector & all_energy, const vector & datype_, const vector & dbox, const int nghost, - const LammpsNeighborList & lmp_list) + const LammpsNeighborList & lmp_list, + const vector fparam) { if (numb_models == 0) return; + if (fparam.size() != dfparam) { + throw std::runtime_error("the dim of frame parameter provided is not consistent with what the model uses"); + } int nall = dcoord_.size() / 3; int nloc = nall - nghost; @@ -820,7 +951,7 @@ compute (vector & all_energy, shuffle_nlist (nlist, nnpmap); std::vector> input_tensors; - int ret = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, nlist, nnpmap, nghost); + int ret = make_input_tensors (input_tensors, dcoord_, ntypes, datype_, dbox, nlist, fparam, nnpmap, nghost); assert (nloc == ret); all_energy.resize (numb_models); diff --git a/source/lib/src/NeighborList.cpp b/source/lib/src/NeighborList.cpp index 9d296335f1..fda1eddbf0 100644 --- a/source/lib/src/NeighborList.cpp +++ b/source/lib/src/NeighborList.cpp @@ -3,6 +3,9 @@ // #include using namespace std; +enum { + MAX_WARN_IDX_OUT_OF_BOUND = 10, +}; bool is_loc (const vector & idx, @@ -44,6 +47,10 @@ build_clist (vector > & clist, const SimulationRegion & region, const vector & global_grid) { + static int count_warning_loc_idx_lower = 0; + static int count_warning_loc_idx_upper = 0; + static int count_warning_ghost_idx_lower = 0; + static int count_warning_ghost_idx_upper = 0; // compute region info, in terms of internal coord int nall = coord.size() / 3; vector ext_ncell(3); @@ -72,12 +79,16 @@ build_clist (vector > & clist, for (int dd = 0; dd < 3; ++dd){ idx[dd] = (inter[dd] - nat_orig[dd]) / cell_size[dd]; if (inter[dd] - nat_orig[dd] < 0.) idx[dd] --; - if (idx[dd] < nat_stt[dd]) { - cerr << "# warning: loc idx out of lower bound " << endl; + if (idx[dd] < nat_stt[dd] && + count_warning_loc_idx_lower < MAX_WARN_IDX_OUT_OF_BOUND) { + cerr << "# warning: loc idx out of lower bound (ignored if warned for more than " << MAX_WARN_IDX_OUT_OF_BOUND << " times) " << endl; + count_warning_loc_idx_lower ++; idx[dd] = nat_stt[dd]; } - else if (idx[dd] >= nat_end[dd]) { - cerr << "# warning: loc idx out of upper bound " << endl; + else if (idx[dd] >= nat_end[dd] && + count_warning_loc_idx_upper < MAX_WARN_IDX_OUT_OF_BOUND) { + cerr << "# warning: loc idx out of upper bound (ignored if warned for more than " << MAX_WARN_IDX_OUT_OF_BOUND << " times) " << endl; + count_warning_loc_idx_upper ++; idx[dd] = nat_end[dd] - 1; } idx[dd] += idx_orig_shift[dd]; @@ -91,16 +102,20 @@ build_clist (vector > & clist, for (int dd = 0; dd < 3; ++dd){ idx[dd] = (inter[dd] - nat_orig[dd]) / cell_size[dd]; if (inter[dd] - nat_orig[dd] < 0.) idx[dd] --; - if (idx[dd] < ext_stt[dd]) { + if (idx[dd] < ext_stt[dd] && + count_warning_ghost_idx_lower < MAX_WARN_IDX_OUT_OF_BOUND) { if (fabs((inter[dd] - nat_orig[dd]) - (ext_stt[dd] * cell_size[dd])) > fabs(ext_stt[dd] * cell_size[dd]) * numeric_limits::epsilon() * 5. ) { - cerr << "# warning: ghost idx out of lower bound " << endl; + cerr << "# warning: ghost idx out of lower bound (ignored if warned for more than " << MAX_WARN_IDX_OUT_OF_BOUND << " times) " << endl; + count_warning_ghost_idx_lower ++; } idx[dd] = ext_stt[dd]; } - else if (idx[dd] >= ext_end[dd]) { - cerr << "# warning: ghost idx out of upper bound " << endl; + else if (idx[dd] >= ext_end[dd] && + count_warning_ghost_idx_upper < MAX_WARN_IDX_OUT_OF_BOUND) { + cerr << "# warning: ghost idx out of upper bound (ignored if warned for more than " << MAX_WARN_IDX_OUT_OF_BOUND << " times) " << endl; + count_warning_ghost_idx_upper ++; idx[dd] = ext_end[dd] - 1; } idx[dd] += idx_orig_shift[dd]; @@ -117,6 +132,8 @@ build_clist (vector > & clist, const vector & nat_end, const SimulationRegion & region) { + static int count_warning_loc_idx_lower = 0; + static int count_warning_loc_idx_upper = 0; // compute region info, in terms of internal coord int nall = coord.size() / 3; vector nat_ncell(3); @@ -144,12 +161,16 @@ build_clist (vector > & clist, for (int dd = 0; dd < 3; ++dd){ idx[dd] = (inter[dd] - nat_orig[dd]) / cell_size[dd]; if (inter[dd] - nat_orig[dd] < 0.) idx[dd] --; - if (idx[dd] < nat_stt[dd]) { - cerr << "# warning: loc idx out of lower bound " << endl; + if (idx[dd] < nat_stt[dd] && + count_warning_loc_idx_lower < MAX_WARN_IDX_OUT_OF_BOUND) { + cerr << "# warning: loc idx out of lower bound (ignored if warned for more than " << MAX_WARN_IDX_OUT_OF_BOUND << " times) " << endl; + count_warning_loc_idx_lower ++; idx[dd] = nat_stt[dd]; } - else if (idx[dd] >= nat_end[dd]) { - cerr << "# warning: loc idx out of upper bound " << endl; + else if (idx[dd] >= nat_end[dd] && + count_warning_loc_idx_upper < MAX_WARN_IDX_OUT_OF_BOUND) { + cerr << "# warning: loc idx out of upper bound (ignored if warned for more than " << MAX_WARN_IDX_OUT_OF_BOUND << " times) " << endl; + count_warning_loc_idx_upper ++; idx[dd] = nat_end[dd] - 1; } } diff --git a/source/lmp/pair_nnp.cpp b/source/lmp/pair_nnp.cpp index b97a7b69c3..ee46fe2d02 100644 --- a/source/lmp/pair_nnp.cpp +++ b/source/lmp/pair_nnp.cpp @@ -39,6 +39,9 @@ PairNNP::PairNNP(LAMMPS *lmp) : Pair(lmp) { + if (strcmp(update->unit_style,"metal") != 0) { + error->all(FLERR,"Pair deepmd requires metal unit, please set it by \"units metal\""); + } pppmflag = 1; respa_enable = 0; writedata = 0; @@ -127,12 +130,15 @@ void PairNNP::compute(int eflag, int vflag) } // compute + bool single_model = (numb_models == 1); + bool multi_models_no_mod_devi = (numb_models > 1 && (out_freq == 0 || update->ntimestep % out_freq != 0)); + bool multi_models_mod_devi = (numb_models > 1 && (out_freq > 0 && update->ntimestep % out_freq == 0)); if (do_ghost) { LammpsNeighborList lmp_list (list->inum, list->ilist, list->numneigh, list->firstneigh); - if (numb_models == 1) { + if (single_model || multi_models_no_mod_devi) { if ( ! (eflag_atom || vflag_atom) ) { #ifdef HIGH_PREC - nnp_inter.compute (dener, dforce, dvirial, dcoord, dtype, dbox, nghost, lmp_list); + nnp_inter.compute (dener, dforce, dvirial, dcoord, dtype, dbox, nghost, lmp_list, fparam); #else vector dcoord_(dcoord.size()); vector dbox_(dbox.size()); @@ -141,7 +147,7 @@ void PairNNP::compute(int eflag, int vflag) vector dforce_(dforce.size(), 0); vector dvirial_(dvirial.size(), 0); double dener_ = 0; - nnp_inter.compute (dener_, dforce_, dvirial_, dcoord_, dtype, dbox_, nghost, lmp_list); + nnp_inter.compute (dener_, dforce_, dvirial_, dcoord_, dtype, dbox_, nghost, lmp_list, fparam); for (unsigned dd = 0; dd < dforce.size(); ++dd) dforce[dd] = dforce_[dd]; for (unsigned dd = 0; dd < dvirial.size(); ++dd) dvirial[dd] = dvirial_[dd]; dener = dener_; @@ -152,7 +158,7 @@ void PairNNP::compute(int eflag, int vflag) vector deatom (nall * 1, 0); vector dvatom (nall * 9, 0); #ifdef HIGH_PREC - nnp_inter.compute (dener, dforce, dvirial, deatom, dvatom, dcoord, dtype, dbox, nghost, lmp_list); + nnp_inter.compute (dener, dforce, dvirial, deatom, dvatom, dcoord, dtype, dbox, nghost, lmp_list, fparam); #else vector dcoord_(dcoord.size()); vector dbox_(dbox.size()); @@ -163,7 +169,7 @@ void PairNNP::compute(int eflag, int vflag) vector deatom_(dforce.size(), 0); vector dvatom_(dforce.size(), 0); double dener_ = 0; - nnp_inter.compute (dener_, dforce_, dvirial_, deatom_, dvatom_, dcoord_, dtype, dbox_, nghost, lmp_list); + nnp_inter.compute (dener_, dforce_, dvirial_, deatom_, dvatom_, dcoord_, dtype, dbox_, nghost, lmp_list, fparam); for (unsigned dd = 0; dd < dforce.size(); ++dd) dforce[dd] = dforce_[dd]; for (unsigned dd = 0; dd < dvirial.size(); ++dd) dvirial[dd] = dvirial_[dd]; for (unsigned dd = 0; dd < deatom.size(); ++dd) deatom[dd] = deatom_[dd]; @@ -185,7 +191,7 @@ void PairNNP::compute(int eflag, int vflag) } } } - else { + else if (multi_models_mod_devi) { vector deatom (nall * 1, 0); vector dvatom (nall * 9, 0); #ifdef HIGH_PREC @@ -193,12 +199,17 @@ void PairNNP::compute(int eflag, int vflag) vector> all_virial; vector> all_atom_energy; vector> all_atom_virial; - nnp_inter_model_devi.compute(all_energy, all_force, all_virial, all_atom_energy, all_atom_virial, dcoord, dtype, dbox, nghost, lmp_list); - nnp_inter_model_devi.compute_avg (dener, all_energy); - nnp_inter_model_devi.compute_avg (dforce, all_force); - nnp_inter_model_devi.compute_avg (dvirial, all_virial); - nnp_inter_model_devi.compute_avg (deatom, all_atom_energy); - nnp_inter_model_devi.compute_avg (dvatom, all_atom_virial); + nnp_inter_model_devi.compute(all_energy, all_force, all_virial, all_atom_energy, all_atom_virial, dcoord, dtype, dbox, nghost, lmp_list, fparam); + // nnp_inter_model_devi.compute_avg (dener, all_energy); + // nnp_inter_model_devi.compute_avg (dforce, all_force); + // nnp_inter_model_devi.compute_avg (dvirial, all_virial); + // nnp_inter_model_devi.compute_avg (deatom, all_atom_energy); + // nnp_inter_model_devi.compute_avg (dvatom, all_atom_virial); + dener = all_energy[0]; + dforce = all_force[0]; + dvirial = all_virial[0]; + deatom = all_atom_energy[0]; + dvatom = all_atom_virial[0]; #else vector dcoord_(dcoord.size()); vector dbox_(dbox.size()); @@ -214,12 +225,17 @@ void PairNNP::compute(int eflag, int vflag) vector> all_virial_; vector> all_atom_energy_; vector> all_atom_virial_; - nnp_inter_model_devi.compute(all_energy_, all_force_, all_virial_, all_atom_energy_, all_atom_virial_, dcoord_, dtype, dbox_, nghost, lmp_list); - nnp_inter_model_devi.compute_avg (dener_, all_energy_); - nnp_inter_model_devi.compute_avg (dforce_, all_force_); - nnp_inter_model_devi.compute_avg (dvirial_, all_virial_); - nnp_inter_model_devi.compute_avg (deatom_, all_atom_energy_); - nnp_inter_model_devi.compute_avg (dvatom_, all_atom_virial_); + nnp_inter_model_devi.compute(all_energy_, all_force_, all_virial_, all_atom_energy_, all_atom_virial_, dcoord_, dtype, dbox_, nghost, lmp_list, fparam); + // nnp_inter_model_devi.compute_avg (dener_, all_energy_); + // nnp_inter_model_devi.compute_avg (dforce_, all_force_); + // nnp_inter_model_devi.compute_avg (dvirial_, all_virial_); + // nnp_inter_model_devi.compute_avg (deatom_, all_atom_energy_); + // nnp_inter_model_devi.compute_avg (dvatom_, all_atom_virial_); + dener_ = all_energy_[0]; + dforce_ = all_force_[0]; + dvirial_ = all_virial_[0]; + deatom_ = all_atom_energy_[0]; + dvatom_ = all_atom_virial_[0]; dener = dener_; for (unsigned dd = 0; dd < dforce.size(); ++dd) dforce[dd] = dforce_[dd]; for (unsigned dd = 0; dd < dvirial.size(); ++dd) dvirial[dd] = dvirial_[dd]; @@ -319,6 +335,9 @@ void PairNNP::compute(int eflag, int vflag) } } } + else { + error->all(FLERR,"unknown computational branch"); + } } else { if (numb_models == 1) { @@ -388,33 +407,90 @@ void PairNNP::allocate() } } + +static bool +is_key (const string& input) +{ + vector keys ; + keys.push_back("out_freq"); + keys.push_back("out_file"); + keys.push_back("fparam"); + + for (int ii = 0; ii < keys.size(); ++ii){ + if (input == keys[ii]) { + return true; + } + } + return false; +} + + void PairNNP::settings(int narg, char **arg) { if (narg <= 0) error->all(FLERR,"Illegal pair_style command"); - if (narg == 1) { + vector models; + int iarg = 0; + while (iarg < narg){ + if (is_key(arg[iarg])) { + break; + } + iarg ++; + } + for (int ii = 0; ii < iarg; ++ii){ + models.push_back(arg[ii]); + } + numb_models = models.size(); + if (numb_models == 1) { nnp_inter.init (arg[0]); cutoff = nnp_inter.cutoff (); numb_types = nnp_inter.numb_types(); - numb_models = 1; + dim_fparam = nnp_inter.dim_fparam(); } else { - if (narg < 4) { - error->all(FLERR,"Illegal pair_style command\nusage:\npair_style deepmd model1 model2 [models...] out_freq out_file\n"); - } - vector models; - for (int ii = 0; ii < narg-2; ++ii){ - models.push_back(arg[ii]); - } - out_freq = atoi(arg[narg-2]); - if (out_freq < 0) error->all(FLERR,"Illegal out_freq, should be >= 0"); - out_file = string(arg[narg-1]); - + nnp_inter.init (arg[0]); nnp_inter_model_devi.init(models); cutoff = nnp_inter_model_devi.cutoff(); numb_types = nnp_inter_model_devi.numb_types(); - numb_models = models.size(); - if (comm->me == 0){ + dim_fparam = nnp_inter_model_devi.dim_fparam(); + assert(cutoff == nnp_inter.cutoff()); + assert(numb_types == nnp_inter.numb_types()); + assert(dim_fparam == nnp_inter.dim_fparam()); + } + + out_freq = 100; + out_file = "model_devi.out"; + fparam.clear(); + while (iarg < narg) { + if (! is_key(arg[iarg])) { + error->all(FLERR,"Illegal pair_style command\nwrong number of parameters\n"); + } + if (string(arg[iarg]) == string("out_freq")) { + if (iarg+1 >= narg) error->all(FLERR,"Illegal out_freq, not provided"); + out_freq = atoi(arg[iarg+1]); + iarg += 2; + } + else if (string(arg[iarg]) == string("out_file")) { + if (iarg+1 >= narg) error->all(FLERR,"Illegal out_file, not provided"); + out_file = string(arg[iarg+1]); + iarg += 2; + } + else if (string(arg[iarg]) == string("fparam")) { + for (int ii = 0; ii < dim_fparam; ++ii){ + if (iarg+1+ii >= narg || is_key(arg[iarg+1+ii])) { + char tmp[1024]; + sprintf(tmp, "Illegal fparam, the dimension should be %d", dim_fparam); + error->all(FLERR, tmp); + } + fparam.push_back(atof(arg[iarg+1+ii])); + } + iarg += 1 + dim_fparam ; + } + } + if (out_freq < 0) error->all(FLERR,"Illegal out_freq, should be >= 0"); + + if (comm->me == 0){ + if (numb_models > 1 && out_freq > 0){ fp.open (out_file); fp << scientific; fp << "#" @@ -427,9 +503,6 @@ void PairNNP::settings(int narg, char **arg) << setw(18+1) << "avg_devi_f" << endl; } - } - - if (comm->me == 0){ string pre = " "; cout << pre << ">>> Info of model(s):" << endl << pre << "using " << setw(3) << numb_models << " model(s): "; @@ -437,13 +510,20 @@ void PairNNP::settings(int narg, char **arg) cout << arg[0] << " "; } else { - for (int ii = 0; ii < narg-2; ++ii){ - cout << arg[ii] << " "; + for (int ii = 0; ii < models.size(); ++ii){ + cout << models[ii] << " "; } } cout << endl << pre << "rcut in model: " << cutoff << endl << pre << "ntypes in model: " << numb_types << endl; + if (dim_fparam > 0) { + cout << pre << "using fparam(s): " ; + for (int ii = 0; ii < dim_fparam; ++ii){ + cout << fparam[ii] << " " ; + } + cout << endl; + } } comm_reverse = numb_models * 3; diff --git a/source/lmp/pair_nnp.h.in b/source/lmp/pair_nnp.h.in index 5e9b04e4a3..edb4aee9aa 100644 --- a/source/lmp/pair_nnp.h.in +++ b/source/lmp/pair_nnp.h.in @@ -78,6 +78,12 @@ private: ofstream fp; int out_freq; string out_file; + int dim_fparam; +#ifdef HIGH_PREC + vector fparam; +#else + vector fparam; +#endif }; } diff --git a/source/op/CMakeLists.txt b/source/op/CMakeLists.txt index 7f58c1ecae..7e600523a7 100644 --- a/source/op/CMakeLists.txt +++ b/source/op/CMakeLists.txt @@ -3,60 +3,74 @@ set(OP_LIB ${PROJECT_SOURCE_DIR}/lib/src/SimulationRegion.cpp ${PROJECT_SOURCE_DIR}/lib/src/NeighborList.cpp) set (OP_CXX_FLAG -D_GLIBCXX_USE_CXX11_ABI=${OP_ABI} ) -file(GLOB OP_SRC prod_force.cc prod_virial.cc descrpt.cc descrpt_norot.cc prod_force_norot.cc prod_virial_norot.cc ) -file(GLOB OP_PY *.py) +file(GLOB OP_SRC prod_force.cc prod_virial.cc descrpt.cc descrpt_norot.cc tab_inter.cc prod_force_norot.cc prod_virial_norot.cc soft_min.cc soft_min_force.cc soft_min_virial.cc ) +file(GLOB OP_GRADS_SRC soft_min_force_grad.cc soft_min_virial_grad.cc) +file(GLOB OP_PY *.py) -add_library(${LIB_DEEPMD_OP} SHARED ${OP_SRC}) -add_library(op_abi SHARED ${OP_SRC} ${OP_LIB}) -add_library(prod_force_grad SHARED prod_force_grad.cc) -add_library(prod_force_norot_grad SHARED prod_force_norot_grad.cc) -add_library(prod_virial_grad SHARED prod_virial_grad.cc) -add_library(prod_virial_norot_grad SHARED prod_virial_norot_grad.cc) +if (BUILD_CPP_IF) + add_library(${LIB_DEEPMD_OP} SHARED ${OP_SRC}) +endif (BUILD_CPP_IF) +if (BUILD_PY_IF) + add_library(op_abi SHARED ${OP_SRC} ${OP_LIB}) + add_library(op_grads SHARED ${OP_GRADS_SRC}) + add_library(prod_force_grad SHARED prod_force_grad.cc) + add_library(prod_force_norot_grad SHARED prod_force_norot_grad.cc) + add_library(prod_virial_grad SHARED prod_virial_grad.cc) + add_library(prod_virial_norot_grad SHARED prod_virial_norot_grad.cc) + target_link_libraries( + op_abi ${TensorFlowFramework_LIBRARY} + ) + target_link_libraries( + op_grads ${TensorFlowFramework_LIBRARY} + ) + target_link_libraries( + prod_force_grad ${TensorFlowFramework_LIBRARY} + ) + target_link_libraries( + prod_force_norot_grad ${TensorFlowFramework_LIBRARY} + ) + target_link_libraries( + prod_virial_grad ${TensorFlowFramework_LIBRARY} + ) + target_link_libraries( + prod_virial_norot_grad ${TensorFlowFramework_LIBRARY} + ) + set_target_properties( + op_abi + PROPERTIES + COMPILE_FLAGS ${OP_CXX_FLAG} + ) + set_target_properties( + prod_force_grad + PROPERTIES + COMPILE_FLAGS ${OP_CXX_FLAG} + ) + set_target_properties( + prod_force_norot_grad + PROPERTIES + COMPILE_FLAGS ${OP_CXX_FLAG} + ) + set_target_properties( + prod_virial_grad + PROPERTIES + COMPILE_FLAGS ${OP_CXX_FLAG} + ) + set_target_properties( + prod_virial_norot_grad + PROPERTIES + COMPILE_FLAGS ${OP_CXX_FLAG} + ) +endif (BUILD_PY_IF) -target_link_libraries( - op_abi ${TensorFlowFramework_LIBRARY} -) -target_link_libraries( - prod_force_grad ${TensorFlowFramework_LIBRARY} -) -target_link_libraries( - prod_force_norot_grad ${TensorFlowFramework_LIBRARY} -) -target_link_libraries( - prod_virial_grad ${TensorFlowFramework_LIBRARY} -) -target_link_libraries( - prod_virial_norot_grad ${TensorFlowFramework_LIBRARY} -) -set_target_properties( - op_abi - PROPERTIES - COMPILE_FLAGS ${OP_CXX_FLAG} -) -set_target_properties( - prod_force_grad - PROPERTIES - COMPILE_FLAGS ${OP_CXX_FLAG} -) -set_target_properties( - prod_force_norot_grad - PROPERTIES - COMPILE_FLAGS ${OP_CXX_FLAG} -) -set_target_properties( - prod_virial_grad - PROPERTIES - COMPILE_FLAGS ${OP_CXX_FLAG} -) -set_target_properties( - prod_virial_norot_grad - PROPERTIES - COMPILE_FLAGS ${OP_CXX_FLAG} -) -install(TARGETS ${LIB_DEEPMD_OP} DESTINATION lib/) -install(TARGETS op_abi DESTINATION lib/deepmd) -install(TARGETS prod_force_grad DESTINATION lib/deepmd) -install(TARGETS prod_force_norot_grad DESTINATION lib/deepmd) -install(TARGETS prod_virial_grad DESTINATION lib/deepmd) -install(TARGETS prod_virial_norot_grad DESTINATION lib/deepmd) -install(FILES ${OP_PY} DESTINATION lib/deepmd) +if (BUILD_CPP_IF) + install(TARGETS ${LIB_DEEPMD_OP} DESTINATION lib/) +endif (BUILD_CPP_IF) +if (BUILD_PY_IF) + install(TARGETS op_abi DESTINATION deepmd) + install(TARGETS op_grads DESTINATION deepmd) + install(TARGETS prod_force_grad DESTINATION deepmd) + install(TARGETS prod_force_norot_grad DESTINATION deepmd) + install(TARGETS prod_virial_grad DESTINATION deepmd) + install(TARGETS prod_virial_norot_grad DESTINATION deepmd) + install(FILES ${OP_PY} DESTINATION deepmd) +endif (BUILD_PY_IF) diff --git a/source/op/_soft_min_force_grad.py b/source/op/_soft_min_force_grad.py new file mode 100644 index 0000000000..8d133e706c --- /dev/null +++ b/source/op/_soft_min_force_grad.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +""" +Gradients for soft min force +""" + +import os +import tensorflow as tf +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import sparse_ops + +module_path = os.path.dirname(os.path.realpath(__file__)) +module_file = os.path.join(module_path, 'libop_grads.so') +assert (os.path.isfile(module_file)), 'module op_grads does not exist' +op_grads_module = tf.load_op_library(module_file) + +@ops.RegisterGradient("SoftMinForce") +def _soft_min_force_grad_cc (op, grad): + net_grad = op_grads_module.soft_min_force_grad (grad, + op.inputs[0], + op.inputs[1], + op.inputs[2], + op.inputs[3], + n_a_sel = op.get_attr("n_a_sel"), + n_r_sel = op.get_attr("n_r_sel")) + return [net_grad, None, None, None] diff --git a/source/op/_soft_min_virial_grad.py b/source/op/_soft_min_virial_grad.py new file mode 100644 index 0000000000..e7e12c3d1c --- /dev/null +++ b/source/op/_soft_min_virial_grad.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +""" +Gradients for soft min virial. +""" + +import os +import tensorflow as tf +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import sparse_ops + +module_path = os.path.dirname(os.path.realpath(__file__)) +module_file = os.path.join(module_path, 'libop_grads.so') +assert (os.path.isfile(module_file)), 'module op_grads does not exist' +op_grads_module = tf.load_op_library(module_file) + +@ops.RegisterGradient("SoftMinVirial") +def _soft_min_virial_grad_cc (op, grad, grad_atom): + net_grad = op_grads_module.soft_min_virial_grad (grad, + op.inputs[0], + op.inputs[1], + op.inputs[2], + op.inputs[3], + op.inputs[4], + n_a_sel = op.get_attr("n_a_sel"), + n_r_sel = op.get_attr("n_r_sel")) + return [net_grad, None, None, None, None] diff --git a/source/op/soft_min.cc b/source/op/soft_min.cc new file mode 100644 index 0000000000..2a70f1c5d6 --- /dev/null +++ b/source/op/soft_min.cc @@ -0,0 +1,202 @@ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +#include "ComputeDescriptor.h" + +using namespace tensorflow; +using namespace std; + +#ifdef HIGH_PREC +typedef double VALUETYPE; +#else +typedef float VALUETYPE; +#endif + +#ifdef HIGH_PREC +REGISTER_OP("SoftMinSwitch") +.Input("type: int32") +.Input("rij: double") +.Input("nlist: int32") +.Input("natoms: int32") +.Attr("sel_a: list(int)") +.Attr("sel_r: list(int)") +.Attr("alpha: float") +.Attr("rmin: float") +.Attr("rmax: float") +.Output("sw_value: double") +.Output("sw_deriv: double"); +#else +REGISTER_OP("SoftMinSwitch") +.Input("type: int32") +.Input("rij: float") +.Input("nlist: int32") +.Input("natoms: int32") +.Attr("sel_a: list(int)") +.Attr("sel_r: list(int)") +.Attr("alpha: float") +.Attr("rmin: float") +.Attr("rmax: float") +.Output("sw_value: float") +.Output("sw_deriv: float"); +#endif + +using namespace tensorflow; + +class SoftMinSwitchOp : public OpKernel { + public: + explicit SoftMinSwitchOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("sel_a", &sel_a)); + OP_REQUIRES_OK(context, context->GetAttr("sel_r", &sel_r)); + OP_REQUIRES_OK(context, context->GetAttr("alpha", &alpha)); + OP_REQUIRES_OK(context, context->GetAttr("rmin", &rmin)); + OP_REQUIRES_OK(context, context->GetAttr("rmax", &rmax)); + cum_sum (sec_a, sel_a); + cum_sum (sec_r, sel_r); + nnei_a = sec_a.back(); + nnei_r = sec_r.back(); + nnei = nnei_a + nnei_r; + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + int tmp_idx = 0; + const Tensor& type_tensor = context->input(tmp_idx++); + const Tensor& rij_tensor = context->input(tmp_idx++); + const Tensor& nlist_tensor = context->input(tmp_idx++); + const Tensor& natoms_tensor = context->input(tmp_idx++); + + // set size of the sample + OP_REQUIRES (context, (type_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of type should be 2")); + OP_REQUIRES (context, (rij_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of rij should be 2")); + OP_REQUIRES (context, (nlist_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of nlist should be 2")); + OP_REQUIRES (context, (natoms_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of natoms should be 1")); + + OP_REQUIRES (context, (natoms_tensor.shape().dim_size(0) >= 3), errors::InvalidArgument ("number of atoms should be larger than (or equal to) 3")); + auto natoms = natoms_tensor .flat(); + + int nframes = type_tensor.shape().dim_size(0); + int nloc = natoms(0); + int nall = natoms(1); + int ntypes = natoms_tensor.shape().dim_size(0) - 2; + assert(sel_a.size() == ntypes); + assert(sel_r.size() == ntypes); + + // check the sizes + OP_REQUIRES (context, (nframes == type_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nframes == rij_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nframes == nlist_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nall == type_tensor.shape().dim_size(1)), errors::InvalidArgument ("shape of type should be nall")); + OP_REQUIRES (context, (3 * nnei * nloc == rij_tensor.shape().dim_size(1)), errors::InvalidArgument ("shape of rij should be 3 * nloc * nnei")); + OP_REQUIRES (context, (nnei * nloc == nlist_tensor.shape().dim_size(1)), errors::InvalidArgument ("shape of nlist should be nloc * nnei")); + + // Create an output tensor + TensorShape sw_value_shape ; + sw_value_shape.AddDim (nframes); + sw_value_shape.AddDim (nloc); + TensorShape sw_deriv_shape ; + sw_deriv_shape.AddDim (nframes); + sw_deriv_shape.AddDim (3 * nnei * nloc); + Tensor* sw_value_tensor = NULL; + Tensor* sw_deriv_tensor = NULL; + tmp_idx = 0; + OP_REQUIRES_OK(context, context->allocate_output(tmp_idx++, sw_value_shape, &sw_value_tensor)); + OP_REQUIRES_OK(context, context->allocate_output(tmp_idx++, sw_deriv_shape, &sw_deriv_tensor )); + + // flat the tensors + auto type = type_tensor .matrix(); + auto rij = rij_tensor .matrix(); + auto nlist = nlist_tensor .matrix(); + auto sw_value = sw_value_tensor ->matrix(); + auto sw_deriv = sw_deriv_tensor ->matrix(); + + // loop over samples +#pragma omp parallel for + for (int kk = 0; kk < nframes; ++kk){ + // fill results with 0 + for (int ii = 0; ii < nloc; ++ii){ + sw_value(kk, ii) = 0; + } + for (int ii = 0; ii < nloc * nnei; ++ii){ + sw_deriv(kk, ii * 3 + 0) = 0; + sw_deriv(kk, ii * 3 + 1) = 0; + sw_deriv(kk, ii * 3 + 2) = 0; + } + // compute force of a frame + for (int ii = 0; ii < nloc; ++ii){ + int i_idx = ii; + VALUETYPE aa = 0; + VALUETYPE bb = 0; + for (int jj = 0; jj < nnei; ++jj){ + int j_idx = nlist (kk, i_idx * nnei + jj); + if (j_idx < 0) continue; + int rij_idx_shift = (i_idx * nnei + jj) * 3; + VALUETYPE dr[3] = { + rij(kk, rij_idx_shift + 0), + rij(kk, rij_idx_shift + 1), + rij(kk, rij_idx_shift + 2) + }; + VALUETYPE rr2 = dr[0] * dr[0] + dr[1] * dr[1] + dr[2] * dr[2]; + VALUETYPE rr = sqrt(rr2); + VALUETYPE ee = exp(-rr / alpha); + aa += ee; + bb += rr * ee; + } + VALUETYPE smin = bb / aa; + VALUETYPE vv, dd; + spline5_switch(vv, dd, smin, rmin, rmax); + // value of switch + sw_value(kk, i_idx) = vv; + // deriv of switch distributed as force + for (int jj = 0; jj < nnei; ++jj){ + int j_idx = nlist (kk, i_idx * nnei + jj); + if (j_idx < 0) continue; + int rij_idx_shift = (ii * nnei + jj) * 3; + VALUETYPE dr[3] = { + rij(kk, rij_idx_shift + 0), + rij(kk, rij_idx_shift + 1), + rij(kk, rij_idx_shift + 2) + }; + VALUETYPE rr2 = dr[0] * dr[0] + dr[1] * dr[1] + dr[2] * dr[2]; + VALUETYPE rr = sqrt(rr2); + VALUETYPE ee = exp(-rr / alpha); + VALUETYPE pref_c = (1./rr - 1./alpha) * ee ; + VALUETYPE pref_d = 1./(rr * alpha) * ee; + VALUETYPE ts; + ts = dd / (aa * aa) * (aa * pref_c + bb * pref_d); + sw_deriv(kk, rij_idx_shift + 0) += ts * dr[0]; + sw_deriv(kk, rij_idx_shift + 1) += ts * dr[1]; + sw_deriv(kk, rij_idx_shift + 2) += ts * dr[2]; + // cout << ii << " " << jj << " " << j_idx << " " + // << vv << " " + // << sw_deriv(kk, rij_idx_shift+0) << " " + // << sw_deriv(kk, rij_idx_shift+1) << " " + // << sw_deriv(kk, rij_idx_shift+2) << " " + // << endl; + } + } + } + } +private: + vector sel_r; + vector sel_a; + vector sec_a; + vector sec_r; + float alpha, rmin, rmax; + int nnei, nnei_a, nnei_r; + void + cum_sum (vector & sec, + const vector & n_sel) const { + sec.resize (n_sel.size() + 1); + sec[0] = 0; + for (int ii = 1; ii < sec.size(); ++ii){ + sec[ii] = sec[ii-1] + n_sel[ii-1]; + } + } +}; + +REGISTER_KERNEL_BUILDER(Name("SoftMinSwitch").Device(DEVICE_CPU), SoftMinSwitchOp); + + + diff --git a/source/op/soft_min_force.cc b/source/op/soft_min_force.cc new file mode 100644 index 0000000000..e51aadbc79 --- /dev/null +++ b/source/op/soft_min_force.cc @@ -0,0 +1,121 @@ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +using namespace tensorflow; +using namespace std; + +#ifdef HIGH_PREC +typedef double VALUETYPE; +#else +typedef float VALUETYPE; +#endif + +#ifdef HIGH_PREC +REGISTER_OP("SoftMinForce") +.Input("du: double") +.Input("sw_deriv: double") +.Input("nlist: int32") +.Input("natoms: int32") +.Attr("n_a_sel: int") +.Attr("n_r_sel: int") +.Output("force: double"); +#else +REGISTER_OP("SoftMinForce") +.Input("du: float") +.Input("sw_deriv: float") +.Input("nlist: int32") +.Input("natoms: int32") +.Attr("n_a_sel: int") +.Attr("n_r_sel: int") +.Output("force: float"); +#endif + +using namespace tensorflow; + +class SoftMinForceOp : public OpKernel { + public: + explicit SoftMinForceOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("n_a_sel", &n_a_sel)); + OP_REQUIRES_OK(context, context->GetAttr("n_r_sel", &n_r_sel)); + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + const Tensor& du_tensor = context->input(0); + const Tensor& sw_deriv_tensor = context->input(1); + const Tensor& nlist_tensor = context->input(2); + const Tensor& natoms_tensor = context->input(3); + + // set size of the sample + OP_REQUIRES (context, (du_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of du should be 2")); + OP_REQUIRES (context, (sw_deriv_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of switch deriv should be 2")); + OP_REQUIRES (context, (nlist_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of nlist should be 2")); + OP_REQUIRES (context, (natoms_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of natoms should be 1")); + + OP_REQUIRES (context, (natoms_tensor.shape().dim_size(0) >= 3), errors::InvalidArgument ("number of atoms should be larger than (or equal to) 3")); + auto natoms = natoms_tensor .flat(); + + int nframes = du_tensor.shape().dim_size(0); + int nloc = natoms(0); + int nall = natoms(1); + int nnei = nlist_tensor.shape().dim_size(1) / nloc; + + // check the sizes + OP_REQUIRES (context, (nframes == sw_deriv_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nframes == nlist_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + + OP_REQUIRES (context, (nloc == du_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of du should match")); + OP_REQUIRES (context, (nloc * nnei * 3 == sw_deriv_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of switch deriv should match")); + OP_REQUIRES (context, (nnei == n_a_sel + n_r_sel), errors::InvalidArgument ("number of neighbors should match")); + + // Create an output tensor + TensorShape force_shape ; + force_shape.AddDim (nframes); + force_shape.AddDim (3 * nall); + Tensor* force_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(0, force_shape, &force_tensor)); + + // flat the tensors + auto du = du_tensor.matrix(); + auto sw_deriv = sw_deriv_tensor.matrix(); + auto nlist = nlist_tensor.matrix(); + auto force = force_tensor->matrix(); + + // loop over samples +#pragma omp parallel for + for (int kk = 0; kk < nframes; ++kk){ + // set zeros + for (int ii = 0; ii < nall; ++ii){ + int i_idx = ii; + force (kk, i_idx * 3 + 0) = 0; + force (kk, i_idx * 3 + 1) = 0; + force (kk, i_idx * 3 + 2) = 0; + } + // compute force of a frame + for (int ii = 0; ii < nloc; ++ii){ + int i_idx = ii; + for (int jj = 0; jj < nnei; ++jj){ + int j_idx = nlist (kk, i_idx * nnei + jj); + if (j_idx < 0) continue; + int rij_idx_shift = (ii * nnei + jj) * 3; + force(kk, i_idx * 3 + 0) += du(kk, i_idx) * sw_deriv(kk, rij_idx_shift + 0); + force(kk, i_idx * 3 + 1) += du(kk, i_idx) * sw_deriv(kk, rij_idx_shift + 1); + force(kk, i_idx * 3 + 2) += du(kk, i_idx) * sw_deriv(kk, rij_idx_shift + 2); + force(kk, j_idx * 3 + 0) -= du(kk, i_idx) * sw_deriv(kk, rij_idx_shift + 0); + force(kk, j_idx * 3 + 1) -= du(kk, i_idx) * sw_deriv(kk, rij_idx_shift + 1); + force(kk, j_idx * 3 + 2) -= du(kk, i_idx) * sw_deriv(kk, rij_idx_shift + 2); + // cout << "soft_min_force " << i_idx << " " << j_idx << " " + // << du(kk, i_idx) << " " + // << du(kk, i_idx) * sw_deriv(kk, rij_idx_shift + 0) + // << endl; + } + } + } + } +private: + int n_r_sel, n_a_sel; +}; + +REGISTER_KERNEL_BUILDER(Name("SoftMinForce").Device(DEVICE_CPU), SoftMinForceOp); diff --git a/source/op/soft_min_force_grad.cc b/source/op/soft_min_force_grad.cc new file mode 100644 index 0000000000..4c8a4b21ff --- /dev/null +++ b/source/op/soft_min_force_grad.cc @@ -0,0 +1,128 @@ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +using namespace tensorflow; +using namespace std; + +#ifdef HIGH_PREC +typedef double VALUETYPE; +#else +typedef float VALUETYPE; +#endif + +#ifdef HIGH_PREC +REGISTER_OP("SoftMinForceGrad") +.Input("grad: double") +.Input("du: double") +.Input("sw_deriv: double") +.Input("nlist: int32") +.Input("natoms: int32") +.Attr("n_a_sel: int") +.Attr("n_r_sel: int") +.Output("grad_net: double"); +#else +REGISTER_OP("SoftMinForceGrad") +.Input("grad: float") +.Input("du: float") +.Input("sw_deriv: float") +.Input("nlist: int32") +.Input("natoms: int32") +.Attr("n_a_sel: int") +.Attr("n_r_sel: int") +.Output("grad_net: float"); +#endif + +class SoftMinForceGradOp : public OpKernel +{ +public: + explicit SoftMinForceGradOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("n_a_sel", &n_a_sel)); + OP_REQUIRES_OK(context, context->GetAttr("n_r_sel", &n_r_sel)); + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + int context_input_index = 0; + const Tensor& grad_tensor = context->input(context_input_index++); + const Tensor& du_tensor = context->input(context_input_index++); + const Tensor& sw_deriv_tensor = context->input(context_input_index++); + const Tensor& nlist_tensor = context->input(context_input_index++); + const Tensor& natoms_tensor = context->input(context_input_index++); + + // set size of the sample + TensorShape grad_shape = grad_tensor.shape(); + TensorShape du_shape = du_tensor.shape(); + TensorShape sw_deriv_shape = sw_deriv_tensor.shape(); + TensorShape nlist_shape = nlist_tensor.shape(); + + OP_REQUIRES (context, (grad_shape.dims() == 2), errors::InvalidArgument ("Dim of grad should be 2")); + OP_REQUIRES (context, (du_shape.dims() == 2), errors::InvalidArgument ("Dim of du should be 2")); + OP_REQUIRES (context, (sw_deriv_shape.dims() == 2), errors::InvalidArgument ("Dim of sw deriv should be 2")); + OP_REQUIRES (context, (nlist_shape.dims() == 2), errors::InvalidArgument ("Dim of nlist should be 2")); + OP_REQUIRES (context, (natoms_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of natoms should be 1")); + + OP_REQUIRES (context, (natoms_tensor.shape().dim_size(0) >= 3), errors::InvalidArgument ("number of atoms should be larger than (or equal to) 3")); + auto natoms = natoms_tensor .flat(); + + int nframes = du_tensor.shape().dim_size(0); + int nloc = natoms(0); + int nnei = nlist_tensor.shape().dim_size(1) / nloc; + + // check the sizes + OP_REQUIRES (context, (nframes == grad_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + OP_REQUIRES (context, (nframes == sw_deriv_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + OP_REQUIRES (context, (nframes == nlist_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + + OP_REQUIRES (context, (nloc * 3 == grad_shape.dim_size(1)), errors::InvalidArgument ("input grad shape should be 3 x natoms")); + OP_REQUIRES (context, (nloc * nnei * 3 == sw_deriv_shape.dim_size(1)),errors::InvalidArgument ("number of sw deriv should match")); + OP_REQUIRES (context, (nnei == n_a_sel + n_r_sel), errors::InvalidArgument ("number of neighbors should match")); + + // Create an output tensor + TensorShape grad_net_shape ; + grad_net_shape.AddDim (nframes); + grad_net_shape.AddDim (nloc); + + // allocate the output tensor + Tensor* grad_net_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(0, grad_net_shape, &grad_net_tensor)); + + // flat the tensors + auto grad = grad_tensor .matrix(); + auto du = du_tensor .matrix(); + auto sw_deriv = sw_deriv_tensor .matrix(); + auto nlist = nlist_tensor .matrix(); + auto grad_net = grad_net_tensor ->matrix(); + + // loop over frames +#pragma omp parallel for + for (int kk = 0; kk < nframes; ++kk){ + // reset the frame to 0 + for (int ii = 0; ii < nloc; ++ii){ + grad_net (kk, ii) = 0; + } + + // compute grad of one frame + for (int ii = 0; ii < nloc; ++ii){ + int i_idx = ii; + // deriv wrt center atom + for (int jj = 0; jj < nnei; ++jj){ + int j_idx = nlist (kk, i_idx * nnei + jj); + if (j_idx < 0) continue; + int rij_idx_shift = (ii * nnei + jj) * 3; + grad_net(kk, i_idx) += grad(kk, i_idx * 3 + 0) * sw_deriv(kk, rij_idx_shift + 0); + grad_net(kk, i_idx) += grad(kk, i_idx * 3 + 1) * sw_deriv(kk, rij_idx_shift + 1); + grad_net(kk, i_idx) += grad(kk, i_idx * 3 + 2) * sw_deriv(kk, rij_idx_shift + 2); + grad_net(kk, i_idx) -= grad(kk, j_idx * 3 + 0) * sw_deriv(kk, rij_idx_shift + 0); + grad_net(kk, i_idx) -= grad(kk, j_idx * 3 + 1) * sw_deriv(kk, rij_idx_shift + 1); + grad_net(kk, i_idx) -= grad(kk, j_idx * 3 + 2) * sw_deriv(kk, rij_idx_shift + 2); + } + } + } + } +private: + int n_r_sel, n_a_sel; +}; + +REGISTER_KERNEL_BUILDER(Name("SoftMinForceGrad").Device(DEVICE_CPU), SoftMinForceGradOp); diff --git a/source/op/soft_min_virial.cc b/source/op/soft_min_virial.cc new file mode 100644 index 0000000000..193c34f981 --- /dev/null +++ b/source/op/soft_min_virial.cc @@ -0,0 +1,141 @@ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +using namespace tensorflow; +using namespace std; + +#ifdef HIGH_PREC +typedef double VALUETYPE; +#else +typedef float VALUETYPE; +#endif + +#ifdef HIGH_PREC +REGISTER_OP("SoftMinVirial") +.Input("du: double") +.Input("sw_deriv: double") +.Input("rij: double") +.Input("nlist: int32") +.Input("natoms: int32") +.Attr("n_a_sel: int") +.Attr("n_r_sel: int") +.Output("virial: double") +.Output("atom_virial: double") +; +#else +REGISTER_OP("SoftMinVirial") +.Input("du: float") +.Input("sw_deriv: float") +.Input("rij: float") +.Input("nlist: int32") +.Input("natoms: int32") +.Attr("n_a_sel: int") +.Attr("n_r_sel: int") +.Output("virial: float") +.Output("atom_virial: float") +; +#endif + +using namespace tensorflow; + +class SoftMinVirialOp : public OpKernel { + public: + explicit SoftMinVirialOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("n_a_sel", &n_a_sel)); + OP_REQUIRES_OK(context, context->GetAttr("n_r_sel", &n_r_sel)); + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + int context_input_index = 0; + const Tensor& du_tensor = context->input(context_input_index++); + const Tensor& sw_deriv_tensor = context->input(context_input_index++); + const Tensor& rij_tensor = context->input(context_input_index++); + const Tensor& nlist_tensor = context->input(context_input_index++); + const Tensor& natoms_tensor = context->input(context_input_index++); + + // set size of the sample + OP_REQUIRES (context, (du_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of net deriv should be 2")); + OP_REQUIRES (context, (sw_deriv_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of input deriv should be 2")); + OP_REQUIRES (context, (rij_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of rij should be 2")); + OP_REQUIRES (context, (nlist_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of nlist should be 2")); + OP_REQUIRES (context, (natoms_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of natoms should be 1")); + + OP_REQUIRES (context, (natoms_tensor.shape().dim_size(0) >= 3), errors::InvalidArgument ("number of atoms should be larger than (or equal to) 3")); + auto natoms = natoms_tensor .flat(); + + int nframes = du_tensor.shape().dim_size(0); + int nloc = natoms(0); + int nall = natoms(1); + int nnei = nlist_tensor.shape().dim_size(1) / nloc; + + // check the sizes + OP_REQUIRES (context, (nframes == sw_deriv_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nframes == rij_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nframes == nlist_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + + OP_REQUIRES (context, (nloc == du_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of du should match")); + OP_REQUIRES (context, (nloc * nnei * 3 == sw_deriv_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of sw_deriv should match")); + OP_REQUIRES (context, (nloc * nnei * 3 == rij_tensor.shape().dim_size(1)), errors::InvalidArgument ("dim of rij should be nnei * 3")); + OP_REQUIRES (context, (nnei == n_a_sel + n_r_sel), errors::InvalidArgument ("number of neighbors should match")); + + // Create an output tensor + TensorShape virial_shape ; + virial_shape.AddDim (nframes); + virial_shape.AddDim (9); + Tensor* virial_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(0, virial_shape, &virial_tensor)); + TensorShape atom_virial_shape ; + atom_virial_shape.AddDim (nframes); + atom_virial_shape.AddDim (9 * nall); + Tensor* atom_virial_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(1, atom_virial_shape, &atom_virial_tensor)); + + // flat the tensors + auto du = du_tensor.matrix(); + auto sw_deriv = sw_deriv_tensor.matrix(); + auto rij = rij_tensor.matrix(); + auto nlist = nlist_tensor.matrix(); + auto virial = virial_tensor->matrix(); + auto atom_virial = atom_virial_tensor->matrix(); + + // loop over samples +#pragma omp parallel for + for (int kk = 0; kk < nframes; ++kk){ + + for (int ii = 0; ii < 9; ++ ii){ + virial (kk, ii) = 0.; + } + for (int ii = 0; ii < 9 * nall; ++ ii){ + atom_virial (kk, ii) = 0.; + } + + // compute virial of a frame + for (int ii = 0; ii < nloc; ++ii){ + int i_idx = ii; + // loop over neighbors + for (int jj = 0; jj < nnei; ++jj){ + int j_idx = nlist (kk, i_idx * nnei + jj); + if (j_idx < 0) continue; + int rij_idx_shift = (ii * nnei + jj) * 3; + for (int dd0 = 0; dd0 < 3; ++dd0){ + for (int dd1 = 0; dd1 < 3; ++dd1){ + VALUETYPE tmp_v = du(kk, i_idx) * sw_deriv(kk, rij_idx_shift + dd0) * rij(kk, rij_idx_shift + dd1); + virial(kk, dd0 * 3 + dd1) -= tmp_v; + atom_virial(kk, j_idx * 9 + dd0 * 3 + dd1) -= tmp_v; + } + } + } + } + } + } +private: + int n_r_sel, n_a_sel; +}; + +REGISTER_KERNEL_BUILDER(Name("SoftMinVirial").Device(DEVICE_CPU), SoftMinVirialOp); + + + diff --git a/source/op/soft_min_virial_grad.cc b/source/op/soft_min_virial_grad.cc new file mode 100644 index 0000000000..6f8703bdee --- /dev/null +++ b/source/op/soft_min_virial_grad.cc @@ -0,0 +1,151 @@ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +using namespace tensorflow; +using namespace std; + +#ifdef HIGH_PREC +typedef double VALUETYPE; +#else +typedef float VALUETYPE; +#endif + +#ifdef HIGH_PREC +REGISTER_OP("SoftMinVirialGrad") +.Input("grad: double") +.Input("du: double") +.Input("sw_deriv: double") +.Input("rij: double") +.Input("nlist: int32") +.Input("natoms: int32") +.Attr("n_a_sel: int") +.Attr("n_r_sel: int") +.Output("grad_net: double"); +#else +REGISTER_OP("SoftMinVirialGrad") +.Input("grad: float") +.Input("du: float") +.Input("sw_deriv: float") +.Input("rij: float") +.Input("nlist: int32") +.Input("natoms: int32") +.Attr("n_a_sel: int") +.Attr("n_r_sel: int") +.Output("grad_net: float"); +#endif + +class SoftMinVirialGradOp : public OpKernel +{ +public: + explicit SoftMinVirialGradOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("n_a_sel", &n_a_sel)); + OP_REQUIRES_OK(context, context->GetAttr("n_r_sel", &n_r_sel)); + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + int context_input_index = 0; + const Tensor& grad_tensor = context->input(context_input_index++); + const Tensor& du_tensor = context->input(context_input_index++); + const Tensor& sw_deriv_tensor = context->input(context_input_index++); + const Tensor& rij_tensor = context->input(context_input_index++); + const Tensor& nlist_tensor = context->input(context_input_index++); + const Tensor& natoms_tensor = context->input(context_input_index++); + + // set size of the sample + TensorShape grad_shape = grad_tensor.shape(); + TensorShape du_shape = du_tensor.shape(); + TensorShape sw_deriv_shape = sw_deriv_tensor.shape(); + TensorShape rij_shape = rij_tensor.shape(); + TensorShape nlist_shape = nlist_tensor.shape(); + + OP_REQUIRES (context, (grad_shape.dims() == 2), errors::InvalidArgument ("Dim of grad should be 2")); + OP_REQUIRES (context, (du_shape.dims() == 2),errors::InvalidArgument ("Dim of net deriv should be 2")); + OP_REQUIRES (context, (sw_deriv_shape.dims() == 2), errors::InvalidArgument ("Dim of input deriv should be 2")); + OP_REQUIRES (context, (rij_shape.dims() == 2), errors::InvalidArgument ("Dim of rij should be 2")); + OP_REQUIRES (context, (nlist_shape.dims() == 2), errors::InvalidArgument ("Dim of nlist should be 2")); + OP_REQUIRES (context, (natoms_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of natoms should be 1")); + + OP_REQUIRES (context, (natoms_tensor.shape().dim_size(0) >= 3), errors::InvalidArgument ("number of atoms should be larger than (or equal to) 3")); + auto natoms = natoms_tensor .flat(); + + int nframes = du_tensor.shape().dim_size(0); + int nloc = natoms(0); + int nnei = nlist_tensor.shape().dim_size(1) / nloc; + + // check the sizes + OP_REQUIRES (context, (nframes == grad_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + OP_REQUIRES (context, (nframes == sw_deriv_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + OP_REQUIRES (context, (nframes == rij_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + OP_REQUIRES (context, (nframes == nlist_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + + OP_REQUIRES (context, (9 == grad_shape.dim_size(1)), errors::InvalidArgument ("input grad shape should be 3 x natoms")); + OP_REQUIRES (context, (nloc == du_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of du should match")); + OP_REQUIRES (context, (nloc * nnei * 3 == sw_deriv_shape.dim_size(1)),errors::InvalidArgument ("number of descriptors should match")); + OP_REQUIRES (context, (nloc * nnei * 3 == rij_shape.dim_size(1)), errors::InvalidArgument ("dim of rij should be nnei * 3")); + OP_REQUIRES (context, (nnei == n_a_sel + n_r_sel), errors::InvalidArgument ("number of neighbors should match")); + + // Create an output tensor + TensorShape grad_net_shape ; + grad_net_shape.AddDim (nframes); + grad_net_shape.AddDim (nloc); + + // allocate the output tensor + Tensor* grad_net_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(0, grad_net_shape, &grad_net_tensor)); + + // flat the tensors + auto grad = grad_tensor .matrix(); + auto du = du_tensor .matrix(); + auto sw_deriv = sw_deriv_tensor .matrix(); + auto rij = rij_tensor .matrix(); + auto nlist = nlist_tensor .matrix(); + auto grad_net = grad_net_tensor ->matrix(); + + // loop over frames +#pragma omp parallel for + for (int kk = 0; kk < nframes; ++kk){ + + // reset the frame to 0 + for (int ii = 0; ii < nloc; ++ii){ + grad_net (kk, ii) = 0; + } + + // compute grad of one frame + for (int ii = 0; ii < nloc; ++ii){ + int i_idx = ii; + // loop over neighbors + for (int jj = 0; jj < nnei; ++jj){ + int j_idx = nlist (kk, i_idx * nnei + jj); + if (j_idx < 0) continue; + int rij_idx_shift = (ii * nnei + jj) * 3; + for (int dd0 = 0; dd0 < 3; ++dd0){ + for (int dd1 = 0; dd1 < 3; ++dd1){ + grad_net (kk, i_idx) -= + grad (kk, dd0 * 3 + dd1) * sw_deriv(kk, rij_idx_shift + dd0) * rij(kk, rij_idx_shift + dd1); + } + } + } + } + } + } +private: + int n_r_sel, n_a_sel, n_a_shift; + inline void + make_descript_range (int & idx_start, + int & idx_end, + const int & nei_idx) { + if (nei_idx < n_a_sel) { + idx_start = nei_idx * 4; + idx_end = nei_idx * 4 + 4; + } + else { + idx_start = n_a_shift + (nei_idx - n_a_sel); + idx_end = n_a_shift + (nei_idx - n_a_sel) + 1; + } + } +}; + +REGISTER_KERNEL_BUILDER(Name("SoftMinVirialGrad").Device(DEVICE_CPU), SoftMinVirialGradOp); diff --git a/source/op/tab_inter.cc b/source/op/tab_inter.cc new file mode 100644 index 0000000000..242e52a6e7 --- /dev/null +++ b/source/op/tab_inter.cc @@ -0,0 +1,324 @@ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +using namespace tensorflow; +using namespace std; + +#ifdef HIGH_PREC +typedef double VALUETYPE; +#else +typedef float VALUETYPE; +#endif + +#ifdef HIGH_PREC +REGISTER_OP("TabInter") +.Input("table_info: double") +.Input("table_data: double") +.Input("type: int32") +.Input("rij: double") +.Input("nlist: int32") +.Input("natoms: int32") +.Input("scale: double") +.Attr("sel_a: list(int)") +.Attr("sel_r: list(int)") +.Output("atom_energy: double") +.Output("force: double") +.Output("atom_virial: double"); +#else +REGISTER_OP("TabInter") +.Input("table_info: double") +.Input("table_data: double") +.Input("type: int32") +.Input("rij: float") +.Input("nlist: int32") +.Input("natoms: int32") +.Input("scale: float") +.Attr("sel_a: list(int)") +.Attr("sel_r: list(int)") +.Output("atom_energy: float") +.Output("force: float") +.Output("atom_virial: float"); +#endif + +using namespace tensorflow; + +inline +void tabulated_inter (double & ener, + double & fscale, + const double * table_info, + const double * table_data, + const double * dr) +{ + // info size: 3 + const double & rmin = table_info[0]; + const double & hh = table_info[1]; + const double hi = 1./hh; + const unsigned nspline = unsigned(table_info[2] + 0.1); + const unsigned ndata = nspline * 4; + + double r2 = dr[0] * dr[0] + dr[1] * dr[1] + dr[2] * dr[2]; + double rr = sqrt(r2); + double uu = (rr - rmin) * hi; + // cout << rr << " " << rmin << " " << hh << " " << uu << endl; + if (uu < 0) { + cerr << "coord go beyond table lower boundary" << endl; + exit(1); + } + int idx = uu; + if (idx >= nspline) { + fscale = ener = 0; + return; + } + uu -= idx; + assert(idx >= 0); + assert(uu >= 0 && uu < 1); + + const double & a3 = table_data[4 * idx + 0]; + const double & a2 = table_data[4 * idx + 1]; + const double & a1 = table_data[4 * idx + 2]; + const double & a0 = table_data[4 * idx + 3]; + + double etmp = (a3 * uu + a2) * uu + a1; + ener = etmp * uu + a0; + fscale = (2. * a3 * uu + a2) * uu + etmp; + fscale *= -hi; +} + +class TabInterOp : public OpKernel { + public: + explicit TabInterOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("sel_a", &sel_a)); + OP_REQUIRES_OK(context, context->GetAttr("sel_r", &sel_r)); + cum_sum (sec_a, sel_a); + cum_sum (sec_r, sel_r); + nnei_a = sec_a.back(); + nnei_r = sec_r.back(); + nnei = nnei_a + nnei_r; + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + int tmp_idx = 0; + const Tensor& table_info_tensor = context->input(tmp_idx++); + const Tensor& table_data_tensor = context->input(tmp_idx++); + const Tensor& type_tensor = context->input(tmp_idx++); + const Tensor& rij_tensor = context->input(tmp_idx++); + const Tensor& nlist_tensor = context->input(tmp_idx++); + const Tensor& natoms_tensor = context->input(tmp_idx++); + const Tensor& scale_tensor = context->input(tmp_idx++); + + // set size of the sample + OP_REQUIRES (context, (table_info_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of table_info should be 1")); + OP_REQUIRES (context, (table_data_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of table_data should be 1")); + OP_REQUIRES (context, (type_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of type should be 2")); + OP_REQUIRES (context, (rij_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of rij should be 2")); + OP_REQUIRES (context, (nlist_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of nlist should be 2")); + OP_REQUIRES (context, (natoms_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of natoms should be 1")); + OP_REQUIRES (context, (scale_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of scale should be 2")); + + OP_REQUIRES (context, (natoms_tensor.shape().dim_size(0) >= 3), errors::InvalidArgument ("number of atoms should be larger than (or equal to) 3")); + auto natoms = natoms_tensor .flat(); + + int nframes = type_tensor.shape().dim_size(0); + int nloc = natoms(0); + int nall = natoms(1); + int ntypes = natoms_tensor.shape().dim_size(0) - 2; + assert(sel_a.size() == ntypes); + assert(sel_r.size() == ntypes); + + // check the sizes + OP_REQUIRES (context, (nframes == type_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nframes == rij_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nframes == nlist_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nall == type_tensor.shape().dim_size(1)), errors::InvalidArgument ("shape of type should be nall")); + OP_REQUIRES (context, (3 * nnei * nloc == rij_tensor.shape().dim_size(1)), errors::InvalidArgument ("shape of rij should be 3 * nloc * nnei")); + OP_REQUIRES (context, (nnei * nloc == nlist_tensor.shape().dim_size(1)), errors::InvalidArgument ("shape of nlist should be nloc * nnei")); + OP_REQUIRES (context, (nloc == scale_tensor.shape().dim_size(1)), errors::InvalidArgument ("shape of scale should be nloc")); + + // Create an output tensor + TensorShape energy_shape ; + energy_shape.AddDim (nframes); + energy_shape.AddDim (nloc); + TensorShape force_shape ; + force_shape.AddDim (nframes); + force_shape.AddDim (3 * nall); + TensorShape virial_shape ; + virial_shape.AddDim (nframes); + virial_shape.AddDim (9 * nall); + Tensor* energy_tensor = NULL; + Tensor* force_tensor = NULL; + Tensor* virial_tensor = NULL; + tmp_idx = 0; + OP_REQUIRES_OK(context, context->allocate_output(tmp_idx++, energy_shape, &energy_tensor)); + OP_REQUIRES_OK(context, context->allocate_output(tmp_idx++, force_shape, &force_tensor )); + OP_REQUIRES_OK(context, context->allocate_output(tmp_idx++, virial_shape, &virial_tensor)); + + // flat the tensors + auto table_info = table_info_tensor.flat(); + auto table_data = table_data_tensor.flat(); + auto type = type_tensor .matrix(); + auto rij = rij_tensor .matrix(); + auto nlist = nlist_tensor .matrix(); + auto scale = scale_tensor .matrix(); + auto energy = energy_tensor ->matrix(); + auto force = force_tensor ->matrix(); + auto virial = virial_tensor ->matrix(); + + OP_REQUIRES (context, (ntypes == int(table_info(3)+0.1)), errors::InvalidArgument ("ntypes provided in table does not match deeppot")); + int nspline = table_info(2)+0.1; + int tab_stride = 4 * nspline; + assert(ntypes * ntypes * tab_stride == table_data_tensor.shape().dim_size(0)); + vector d_table_info(4); + vector d_table_data(ntypes * ntypes * tab_stride); + for (unsigned ii = 0; ii < d_table_info.size(); ++ii){ + d_table_info[ii] = table_info(ii); + } + for (unsigned ii = 0; ii < d_table_data.size(); ++ii){ + d_table_data[ii] = table_data(ii); + } + const double * p_table_info = &(d_table_info[0]); + const double * p_table_data = &(d_table_data[0]); + + // loop over samples +#pragma omp parallel for + for (int kk = 0; kk < nframes; ++kk){ + // fill results with 0 + for (int ii = 0; ii < nloc; ++ii){ + int i_idx = ii; + energy(kk, i_idx) = 0; + } + for (int ii = 0; ii < nall; ++ii){ + int i_idx = ii; + force(kk, i_idx * 3 + 0) = 0; + force(kk, i_idx * 3 + 1) = 0; + force(kk, i_idx * 3 + 2) = 0; + for (int dd = 0; dd < 9; ++dd) { + virial(kk, i_idx * 9 + dd) = 0; + } + } + // compute force of a frame + int i_idx = 0; + for (int tt = 0; tt < ntypes; ++tt) { + for (int ii = 0; ii < natoms(2+tt); ++ii){ + int i_type = type(kk, i_idx); + VALUETYPE i_scale = scale(kk, i_idx); + assert(i_type == tt) ; + int jiter = 0; + // a neighbor + for (int ss = 0; ss < sel_a.size(); ++ss){ + int j_type = ss; + const double * cur_table_data = + p_table_data + (i_type * ntypes + j_type) * tab_stride; + for (int jj = 0; jj < sel_a[ss]; ++jj){ + int j_idx = nlist(kk, i_idx * nnei + jiter); + if (j_idx < 0){ + jiter++; + continue; + } + assert(j_type == type(kk, j_idx)); + double dr[3]; + for (int dd = 0; dd < 3; ++dd){ + dr[dd] = rij(kk, (i_idx * nnei + jiter) * 3 + dd); + } + double r2 = dr[0] * dr[0] + dr[1] * dr[1] + dr[2] * dr[2]; + double ri = 1./sqrt(r2); + double ener, fscale; + tabulated_inter(ener, + fscale, + p_table_info, + cur_table_data, + dr); + // printf("tabforce %d %d r: %12.8f ener: %12.8f %12.8f %8.5f fj: %8.5f %8.5f %8.5f dr: %9.6f %9.6f %9.6f\n", + // i_idx, j_idx, + // 1/ri, + // ener, fscale, i_scale, + // -fscale * dr[00] * ri * 0.5 * i_scale, -fscale * dr[01] * ri * 0.5 * i_scale, -fscale * dr[02] * ri * 0.5 * i_scale, + // dr[0], dr[1], dr[2] + // ); + energy(kk, i_idx) += 0.5 * ener; + for (int dd = 0; dd < 3; ++dd) { + force(kk, i_idx * 3 + dd) -= fscale * dr[dd] * ri * 0.5 * i_scale; + force(kk, j_idx * 3 + dd) += fscale * dr[dd] * ri * 0.5 * i_scale; + } + for (int dd0 = 0; dd0 < 3; ++dd0) { + for (int dd1 = 0; dd1 < 3; ++dd1) { + virial(kk, i_idx * 9 + dd0 * 3 + dd1) + += 0.5 * fscale * dr[dd0] * dr[dd1] * ri * 0.5 * i_scale; + virial(kk, j_idx * 9 + dd0 * 3 + dd1) + += 0.5 * fscale * dr[dd0] * dr[dd1] * ri * 0.5 * i_scale; + } + } + jiter++; + } + } + // r neighbor + for (int ss = 0; ss < sel_r.size(); ++ss){ + int j_type = ss; + const double * cur_table_data = + p_table_data + (i_type * ntypes + j_type) * tab_stride; + for (int jj = 0; jj < sel_r[ss]; ++jj){ + int j_idx = nlist(kk, i_idx * nnei + jiter); + if (j_idx < 0){ + jiter ++; + continue; + } + assert(j_type == type(kk, j_idx)); + double dr[3]; + for (int dd = 0; dd < 3; ++dd){ + dr[dd] = rij(kk, (i_idx * nnei + jiter) * 3 + dd); + } + double r2 = dr[0] * dr[0] + dr[1] * dr[1] + dr[2] * dr[2]; + double ri = 1./sqrt(r2); + double ener, fscale; + tabulated_inter(ener, + fscale, + p_table_info, + cur_table_data, + dr); + // printf("tabforce %d %d %8.5f %12.8f %12.8f %8.5f fj: %8.5f %8.5f %8.5f\n", + // i_idx, j_idx, + // 1/ri, + // ener, fscale, i_scale, + // -fscale * dr[00] * ri * 0.5 * i_scale, -fscale * dr[01] * ri * 0.5 * i_scale, -fscale * dr[02] * ri * 0.5 * i_scale); + energy(kk, i_idx) += 0.5 * ener; + for (int dd = 0; dd < 3; ++dd) { + force(kk, i_idx * 3 + dd) -= fscale * dr[dd] * ri * 0.5 * i_scale; + force(kk, j_idx * 3 + dd) += fscale * dr[dd] * ri * 0.5 * i_scale; + } + for (int dd0 = 0; dd0 < 3; ++dd0) { + for (int dd1 = 0; dd1 < 3; ++dd1) { + virial(kk, j_idx * 9 + dd0 * 3 + dd1) + += fscale * dr[dd0] * dr[dd1] * ri * 0.5 * i_scale; + } + } + jiter++; + } + } + i_idx ++; + } + } + } + } +private: + vector sel_r; + vector sel_a; + vector sec_a; + vector sec_r; + int nnei, nnei_a, nnei_r; + void + cum_sum (vector & sec, + const vector & n_sel) const { + sec.resize (n_sel.size() + 1); + sec[0] = 0; + for (int ii = 1; ii < sec.size(); ++ii){ + sec[ii] = sec[ii-1] + n_sel[ii-1]; + } + } +}; + +REGISTER_KERNEL_BUILDER(Name("TabInter").Device(DEVICE_CPU), TabInterOp); + + + diff --git a/source/pyproject.toml b/source/pyproject.toml new file mode 100644 index 0000000000..75335283e8 --- /dev/null +++ b/source/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +requires = ["setuptools", "wheel", "scikit-build", "cmake", "ninja"] diff --git a/source/scripts/CMakeLists.txt b/source/scripts/CMakeLists.txt index 4d48b13085..c78bbc8d5a 100644 --- a/source/scripts/CMakeLists.txt +++ b/source/scripts/CMakeLists.txt @@ -1,5 +1,5 @@ install( - PROGRAMS freeze.py - DESTINATION bin/ - RENAME dp_frz + FILES freeze.py config.py + DESTINATION deepmd/ ) + diff --git a/source/scripts/config.py b/source/scripts/config.py new file mode 100644 index 0000000000..6e87cf8329 --- /dev/null +++ b/source/scripts/config.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 + +import glob,os,sys,json,argparse +import numpy as np + + +def valid_dir(name) : + if not os.path.isfile(os.path.join(name, 'type.raw')) : + raise OSError + sets = glob.glob(os.path.join(name, 'set.*')) + for ii in sets : + if not os.path.isfile(os.path.join(ii, 'box.npy')) : + raise OSError + if not os.path.isfile(os.path.join(ii, 'coord.npy')) : + raise OSError + + +def load_systems(dirs) : + all_type = [] + all_box = [] + for ii in dirs : + sys_type = np.loadtxt(os.path.join(ii, 'type.raw'), dtype = int) + sys_box = None + sets = glob.glob(os.path.join(ii, 'set.*')) + for ii in sets : + if type(sys_box) is not np.ndarray : + sys_box = np.load(os.path.join(ii, 'box.npy')) + else : + sys_box = np.concatenate((sys_box, np.load(os.path.join(ii, 'box.npy'))), axis = 0) + all_type.append(sys_type) + all_box.append(sys_box) + return all_type, all_box + + +def get_system_names() : + dirs = input("Enter system path(s) (seperated by space, wide card supported): \n") + dirs = dirs.split() + real_dirs = [] + for ii in dirs : + real_dirs += glob.glob(ii) + for ii in real_dirs : + valid_dir(ii) + return real_dirs + +def get_rcut() : + dv = 6 + rcut = input("Enter rcut (default %f A): \n" % dv) + try: + rcut = float(rcut) + except ValueError: + rcut = dv + if rcut <= 0: + raise ValueError('rcut should be > 0') + return rcut + + +def get_batch_size_rule() : + dv = 32 + matom = input("Enter the minimal number of atoms in a batch (default %d): \n" % dv) + try: + matom = int(matom) + except ValueError: + matom = dv + if matom <= 0: + raise ValueError('the number should be > 0') + return matom + + +def get_stop_batch(): + dv = 1000000 + sb = input("Enter the stop batch (default %d): \n" % dv) + try: + sb = int(sb) + except ValueError: + sb = dv + if sb <= 0: + raise ValueError('the number should be > 0') + return sb + + +def get_ntypes (all_type) : + coll = [] + for ii in all_type: + coll += list(ii) + list_coll = set(coll) + return len(list_coll) + + +def get_max_density(all_type, all_box) : + ntypes = get_ntypes(all_type) + all_max = [] + for tt, bb in zip(all_type, all_box) : + vv = np.reshape(bb, [-1,3,3]) + vv = np.linalg.det(vv) + min_v = np.min(vv) + type_count = [] + for ii in range(ntypes) : + type_count.append(sum(tt == ii)) + max_den = type_count / min_v + all_max.append(max_den) + all_max = np.max(all_max, axis = 0) + return all_max + + + + +def suggest_sel(all_type, all_box, rcut, ratio = 1.5) : + max_den = get_max_density(all_type, all_box) + return [int(ii) for ii in max_den * 4./3. * np.pi * rcut**3 * ratio] + + +def suggest_batch_size(all_type, min_atom) : + bs = [] + for ii in all_type : + natoms = len(ii) + tbs = min_atom // natoms + if (min_atom // natoms) * natoms != min_atom : + tbs += 1 + bs.append(tbs) + return bs + + +def suggest_decay(sb): + decay_steps = int(sb // 200) + decay_rate = 0.95 + return decay_steps, decay_rate + + +def default_data() : + data = {} + data['use_smooth'] = True + data['sel_a'] = [] + data['rcut_smth'] = -1 + data['rcut'] = -1 + data['filter_neuron'] = [20, 40, 80] + data['filter_resnet_dt'] = False + data['axis_neuron'] = 8 + data['fitting_neuron'] = [240, 240, 240] + data['fitting_resnet_dt'] = True + data['coord_norm'] = True + data['type_fitting_net'] = False + data['systems'] = [] + data['set_prefix'] = 'set' + data['stop_batch'] = -1 + data['batch_size'] = -1 + data['start_lr'] = 0.001 + data['decay_steps'] = -1 + data['decay_rate'] = 0.95 + data['start_pref_e'] = 0.02 + data['limit_pref_e'] = 1 + data['start_pref_f'] = 1000 + data['limit_pref_f'] = 1 + data['start_pref_v'] = 0 + data['limit_pref_v'] = 0 + data['seed'] = 1 + data['disp_file'] = 'lcurve.out' + data['disp_freq'] = 1000 + data['numb_test'] = 10 + data['save_freq'] = 10000 + data["save_ckpt"] = "model.ckpt" + data["disp_training"] = True + data["time_training"] = True + return data + + +def config(args) : + all_sys = get_system_names() + if len(all_sys) == 0 : + raise RuntimeError('no system specified') + rcut = get_rcut() + matom = get_batch_size_rule() + stop_batch = get_stop_batch() + + all_type, all_box = load_systems(all_sys) + sel = suggest_sel(all_type, all_box, rcut, ratio = 1.5) + bs = suggest_batch_size(all_type, matom) + decay_steps, decay_rate = suggest_decay(stop_batch) + + jdata = default_data() + jdata['systems'] = all_sys + jdata['sel_a'] = sel + jdata['rcut'] = rcut + jdata['rcut_smth'] = rcut - 0.2 + jdata['stop_batch'] = stop_batch + jdata['batch_size'] = bs + jdata['decay_steps'] = decay_steps + jdata['decay_rate'] = decay_rate + + with open(args.output, 'w') as fp: + json.dump(jdata, fp, indent=4) + diff --git a/source/scripts/freeze.py b/source/scripts/freeze.py index 5e1899f28b..d5d82fb94c 100755 --- a/source/scripts/freeze.py +++ b/source/scripts/freeze.py @@ -11,7 +11,7 @@ from tensorflow.python.framework import ops # load force module -module_path = os.path.dirname(os.path.realpath(__file__)) + "/../lib/" +module_path = os.path.dirname(os.path.realpath(__file__)) + "/../" assert (os.path.isfile (module_path + "deepmd/libop_abi.so" )), "force module does not exist" op_module = tf.load_op_library(module_path + "deepmd/libop_abi.so") @@ -21,6 +21,8 @@ import deepmd._prod_virial_grad import deepmd._prod_force_norot_grad import deepmd._prod_virial_norot_grad +import deepmd._soft_min_force_grad +import deepmd._soft_min_virial_grad def freeze_graph(model_folder, output, @@ -65,17 +67,5 @@ def freeze_graph(model_folder, print("%d ops in the final graph." % len(output_graph_def.node)) -if __name__ == '__main__': - - default_frozen_nodes = "energy_test,force_test,virial_test,atom_energy_test,atom_virial_test,t_rcut,t_ntypes" - - parser = argparse.ArgumentParser() - parser.add_argument("-d", "--folder", type=str, default = ".", - help="path to checkpoint folder") - parser.add_argument("-o", "--output", type=str, default = "frozen_model.pb", - help="name of graph, will output to the checkpoint folder") - parser.add_argument("-n", "--nodes", type=str, default = default_frozen_nodes, - help="the frozen nodes, defaults is " + default_frozen_nodes) - args = parser.parse_args() - +def freeze (args): freeze_graph(args.folder, args.output, args.nodes) diff --git a/source/setup.py b/source/setup.py new file mode 100644 index 0000000000..cd3fbfc3c7 --- /dev/null +++ b/source/setup.py @@ -0,0 +1,40 @@ +from skbuild import setup +from os import path +import imp + +readme_file = path.join(path.dirname(path.abspath(__file__)), '..', 'README.md') +try: + from m2r import parse_from_file + readme = parse_from_file(readme_file) +except ImportError: + with open(readme_file) as f: + readme = f.read() + + +tf_install_dir = imp.find_module('tensorflow')[1] + +# install_requires = ['xml'] +install_requires=[] + +setup( + name="deepmd", + version_format='{tag}.dev{commitcount}+{gitsha}', + author="Han Wang", + author_email="wang_han@iapcm.ac.cn", + description="Manipulating DeePMD-kit, VASP and LAMMPS data formats", + long_description=readme, + long_description_content_type="text/markdown", + url="https://github.com/deepmodeling/deepmd-kit", + packages=['deepmd'], + classifiers=[ + "Programming Language :: Python :: 3.6", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + ], + keywords='deepmd', + install_requires=install_requires, + cmake_args=['-DTENSORFLOW_ROOT:STRING=%s' % tf_install_dir, + '-DTF_GOOGLE_BIN:BOOL=FALSE', + '-DBUILD_PY_IF:BOOL=TRUE', + '-DBUILD_CPP_IF:BOOL=FALSE', + ], +) diff --git a/source/tests/CMakeLists.txt b/source/tests/CMakeLists.txt new file mode 100644 index 0000000000..cb98e3ba68 --- /dev/null +++ b/source/tests/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB LIB_PY test_descrpt_nonsmth.py test_descrpt_smooth.py test_tab_nonsmth.py test_tab_smooth.py common.py) + +install( + FILES ${LIB_PY} + DESTINATION deepmd/tests +) + diff --git a/source/tests/common.py b/source/tests/common.py new file mode 100644 index 0000000000..7e130f04ec --- /dev/null +++ b/source/tests/common.py @@ -0,0 +1,237 @@ +import tensorflow as tf +import numpy as np + + +class Data(): + def __init__ (self) : + coord = [[0.0, 0.0, 0.1], [1.1, 0.0, 0.1], [0.0, 1.1, 0.1], + [4.0, 0.0, 0.0], [5.1, 0.0, 0.0], [4.0, 1.1, 0.0]] + self.coord = np.array(coord) + self.coord += 0.1 * np.random.random(self.coord.shape) + self.atype = np.array([0, 1, 1, 0, 1, 1], dtype = int) + self.cell = 20 * np.eye(3) + self.nframes = 1 + self.coord = self.coord.reshape([self.nframes, -1]) + self.cell = self.cell.reshape([self.nframes, -1]) + self.natoms = len(self.atype) + self.idx_map = np.lexsort ((np.arange(self.natoms), self.atype)) + self.coord = self.coord.reshape([1, -1, 3]) + self.coord = self.coord[:,self.idx_map,:] + self.coord = self.coord.reshape([1, -1]) + self.atype = self.atype[self.idx_map] + self.datype = np.tile(self.atype, [self.nframes,1]) + + def get_data(self) : + return self.coord, self.cell, self.datype + + def get_natoms (self) : + ret = [self.natoms, self.natoms] + for ii in range(max(self.atype) + 1) : + ret.append(np.sum(self.atype == ii)) + return np.array(ret, dtype = np.int32) + + def get_ntypes(self) : + return max(self.atype) + 1 + + def get_test_box_data (self, + hh) : + coord0_, box0_, type0_ = self.get_data() + coord0 = coord0_[0] + box0 = box0_[0] + type0 = type0_[0] + nc = np.array( [coord0, coord0*(1+hh), coord0*(1-hh)] ) + nb = np.array( [box0, box0*(1+hh), box0*(1-hh)] ) + nt = np.array( [type0, type0, type0] ) + for dd in range(3) : + tmpc = np.copy (coord0) + tmpb = np.copy (box0) + tmpc = np.reshape(tmpc, [-1, 3]) + tmpc [:,dd] *= (1+hh) + tmpc = np.reshape(tmpc, [-1]) + tmpb = np.reshape(tmpb, [-1, 3]) + tmpb [dd,:] *= (1+hh) + tmpb = np.reshape(tmpb, [-1]) + nc = np.append (nc, [tmpc], axis = 0) + nb = np.append (nb, [tmpb], axis = 0) + nt = np.append (nt, [type0], axis = 0) + tmpc = np.copy (coord0) + tmpb = np.copy (box0) + tmpc = np.reshape(tmpc, [-1, 3]) + tmpc [:,dd] *= (1-hh) + tmpc = np.reshape(tmpc, [-1]) + tmpb = np.reshape(tmpb, [-1, 3]) + tmpb [dd,:] *= (1-hh) + tmpb = np.reshape(tmpb, [-1]) + nc = np.append (nc, [tmpc], axis = 0) + nb = np.append (nb, [tmpb], axis = 0) + nt = np.append (nt, [type0], axis = 0) + return nc, nb, nt + + +def force_test (inter, + testCase, + places = 6, + hh = 1e-6, + suffix = '') : + # set weights + w0 = np.ones (inter.ndescrpt) + inter.net_w_i = np.copy(w0) + # make network + t_energy, t_force, t_virial \ + = inter.comp_ef (inter.coord, inter.box, inter.type, inter.tnatoms, name = "test_f" + suffix) + inter.sess.run (tf.global_variables_initializer()) + # get data + dcoord, dbox, dtype = inter.data.get_data () + # cmp e0, f0 + [energy, force] = inter.sess.run ([t_energy, t_force], + feed_dict = { + inter.coord: dcoord, + inter.box: dbox, + inter.type: dtype, + inter.tnatoms: inter.natoms} + ) + # dim force + sel_idx = np.arange(inter.natoms[0]) + for idx in sel_idx: + for dd in range(3): + dcoordp = np.copy(dcoord) + dcoordm = np.copy(dcoord) + dcoordp[0,idx*3+dd] = dcoord[0,idx*3+dd] + hh + dcoordm[0,idx*3+dd] = dcoord[0,idx*3+dd] - hh + [enerp] = inter.sess.run ([t_energy], + feed_dict = { + inter.coord: dcoordp, + inter.box: dbox, + inter.type: dtype, + inter.tnatoms: inter.natoms} + ) + [enerm] = inter.sess.run ([t_energy], + feed_dict = { + inter.coord: dcoordm, + inter.box: dbox, + inter.type: dtype, + inter.tnatoms: inter.natoms} + ) + c_force = -(enerp[0] - enerm[0]) / (2*hh) + testCase.assertAlmostEqual(c_force, force[0,idx*3+dd], + places = places, + msg = "force component [%d,%d] failed" % (idx, dd)) + +def comp_vol (box) : + return np.linalg.det (np.reshape(box, (3,3))) + +def virial_test (inter, + testCase, + places = 6, + hh = 1e-6, + suffix = '') : + # set weights + w0 = np.ones (inter.ndescrpt) + inter.net_w_i = np.copy(w0) + # make network + t_energy, t_force, t_virial \ + = inter.comp_ef (inter.coord, inter.box, inter.type, inter.tnatoms, name = "test_v" + suffix) + inter.sess.run (tf.global_variables_initializer()) + # get data + dcoord, dbox, dtype = inter.data.get_test_box_data(hh) + # cmp e, f, v + [energy, force, virial] \ + = inter.sess.run ([t_energy, t_force, t_virial], + feed_dict = { + inter.coord: dcoord, + inter.box: dbox, + inter.type: dtype, + inter.tnatoms: inter.natoms} + ) + # check + ana_vir3 = (virial[0][0] + virial[0][4] + virial[0][8])/3. / comp_vol(dbox[0]) + num_vir3 = -(energy[1] - energy[2]) / (comp_vol(dbox[1]) - comp_vol(dbox[2])) + testCase.assertAlmostEqual(ana_vir3, num_vir3) + vir_idx = [0, 4, 8] + for dd in range (3) : + ana_v = (virial[0][vir_idx[dd]] / comp_vol(dbox[0])) + idx = 2 * (dd+1) + 1 + num_v = ( -(energy[idx] - energy[idx+1]) / (comp_vol(dbox[idx]) - comp_vol(dbox[idx+1])) ) + testCase.assertAlmostEqual(ana_v, num_v) + + +def force_dw_test (inter, + testCase, + places = 6, + hh = 1e-4, + suffix = '') : + dcoord, dbox, dtype = inter.data.get_data() + feed_dict_test0 = { + inter.coord: dcoord, + inter.box: dbox, + inter.type: dtype, + inter.tnatoms: inter.natoms} + + w0 = np.ones (inter.ndescrpt) + inter.net_w_i = np.copy(w0) + + t_ll, t_dw = inter.comp_f_dw (inter.coord, inter.box, inter.type, inter.tnatoms, name = "f_dw_test_0" + suffix) + inter.sess.run (tf.global_variables_initializer()) + ll_0 = inter.sess.run (t_ll, feed_dict = feed_dict_test0) + dw_0 = inter.sess.run (t_dw, feed_dict = feed_dict_test0) + + absolut_e = [] + relativ_e = [] + test_list = range (inter.ndescrpt) + ntest = 3 + test_list = np.concatenate((np.arange(0,ntest), np.arange(inter.sel_a[0]*4, inter.sel_a[0]*4+ntest))) + for ii in test_list: + inter.net_w_i = np.copy (w0) + inter.net_w_i[ii] += hh + t_ll, t_dw = inter.comp_f_dw (inter.coord, inter.box, inter.type, inter.tnatoms, name = "f_dw_test_" + str(ii*2+1) + suffix) + inter.sess.run (tf.global_variables_initializer()) + ll_1 = inter.sess.run (t_ll, feed_dict = feed_dict_test0) + inter.net_w_i[ii] -= 2. * hh + t_ll, t_dw = inter.comp_f_dw (inter.coord, inter.box, inter.type, inter.tnatoms, name = "f_dw_test_" + str(ii*2+2) + suffix) + inter.sess.run (tf.global_variables_initializer()) + ll_2 = inter.sess.run (t_ll, feed_dict = feed_dict_test0) + num_v = (ll_1 - ll_2) / (2. * hh) + ana_v = dw_0[ii] + diff = np.abs (num_v - ana_v) + # print(ii, num_v, ana_v) + testCase.assertAlmostEqual(num_v, ana_v, places = places) + + +def virial_dw_test (inter, + testCase, + places = 6, + hh = 1e-4, + suffix = '') : + dcoord, dbox, dtype = inter.data.get_data() + feed_dict_test0 = { + inter.coord: dcoord, + inter.box: dbox, + inter.type: dtype, + inter.tnatoms: inter.natoms} + + w0 = np.ones (inter.ndescrpt) + inter.net_w_i = np.copy(w0) + + t_ll, t_dw = inter.comp_v_dw (inter.coord, inter.box, inter.type, inter.tnatoms, name = "v_dw_test_0" + suffix) + inter.sess.run (tf.global_variables_initializer()) + ll_0 = inter.sess.run (t_ll, feed_dict = feed_dict_test0) + dw_0 = inter.sess.run (t_dw, feed_dict = feed_dict_test0) + + absolut_e = [] + relativ_e = [] + test_list = range (inter.ndescrpt) + ntest = 3 + test_list = np.concatenate((np.arange(0,ntest), np.arange(inter.sel_a[0]*4, inter.sel_a[0]*4+ntest))) + for ii in test_list: + inter.net_w_i = np.copy (w0) + inter.net_w_i[ii] += hh + t_ll, t_dw = inter.comp_v_dw (inter.coord, inter.box, inter.type, inter.tnatoms, name = "v_dw_test_" + str(ii*2+1) + suffix) + inter.sess.run (tf.global_variables_initializer()) + ll_1 = inter.sess.run (t_ll, feed_dict = feed_dict_test0) + inter.net_w_i[ii] -= 2. * hh + t_ll, t_dw = inter.comp_v_dw (inter.coord, inter.box, inter.type, inter.tnatoms, name = "v_dw_test_" + str(ii*2+2) + suffix) + inter.sess.run (tf.global_variables_initializer()) + ll_2 = inter.sess.run (t_ll, feed_dict = feed_dict_test0) + num_v = (ll_1 - ll_2) / (2. * hh) + ana_v = dw_0[ii] + testCase.assertAlmostEqual(num_v, ana_v, places = places) diff --git a/source/tests/test_descrpt_nonsmth.py b/source/tests/test_descrpt_nonsmth.py new file mode 100644 index 0000000000..9184791ae7 --- /dev/null +++ b/source/tests/test_descrpt_nonsmth.py @@ -0,0 +1,202 @@ +import os,sys +import numpy as np +import tensorflow as tf +import unittest + +from tensorflow.python.framework import ops + +# load force module +module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") +so_file = os.path.join(module_path, "libop_abi.so") +assert (os.path.isfile ( so_file )), "op module does not exist" +op_module = tf.load_op_library( so_file ) + +# load grad of force module +sys.path.append (module_path) +import _prod_force_grad +import _prod_virial_grad +import _soft_min_force_grad +import _soft_min_virial_grad + +from common import force_test +from common import virial_test +from common import force_dw_test +from common import virial_dw_test +from common import Data + +class Inter(): + def __init__ (self, + data, + comp = 0) : + self.sess = tf.Session() + self.data = data + self.natoms = self.data.get_natoms() + self.ntypes = self.data.get_ntypes() + self.sel_a = [12,24] + self.sel_r = [12,24] + self.rcut_a = -1 + self.rcut_r = 10.0 + self.axis_rule = [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0] + self.nnei_a = np.cumsum(self.sel_a)[-1] + self.nnei_r = np.cumsum(self.sel_r)[-1] + self.nnei = self.nnei_a + self.nnei_r + self.ndescrpt_a = self.nnei_a * 4 + self.ndescrpt_r = self.nnei_r * 1 + self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r + davg = np.zeros ([self.ntypes, self.ndescrpt]) + dstd = np.ones ([self.ntypes, self.ndescrpt]) + self.t_avg = tf.constant(davg.astype(np.float64)) + self.t_std = tf.constant(dstd.astype(np.float64)) + self.default_mesh = np.zeros (6, dtype = np.int32) + self.default_mesh[3] = 2 + self.default_mesh[4] = 2 + self.default_mesh[5] = 2 + # make place holder + self.coord = tf.placeholder(tf.float64, [None, self.natoms[0] * 3], name='t_coord') + self.box = tf.placeholder(tf.float64, [None, 9], name='t_box') + self.type = tf.placeholder(tf.int32, [None, self.natoms[0]], name = "t_type") + self.tnatoms = tf.placeholder(tf.int32, [None], name = "t_natoms") + + def _net (self, + inputs, + name, + reuse = False) : + with tf.variable_scope(name, reuse=reuse): + net_w = tf.get_variable ('net_w', + [self.ndescrpt], + tf.float64, + tf.constant_initializer (self.net_w_i)) + dot_v = tf.matmul (tf.reshape (inputs, [-1, self.ndescrpt]), + tf.reshape (net_w, [self.ndescrpt, 1])) + return tf.reshape (dot_v, [-1]) + + def comp_ef (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + descrpt, descrpt_deriv, rij, nlist, axis \ + = op_module.descrpt (dcoord, + dtype, + tnatoms, + dbox, + tf.constant(self.default_mesh), + self.t_avg, + self.t_std, + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + sel_a = self.sel_a, + sel_r = self.sel_r, + axis_rule = self.axis_rule) + self.axis = axis + self.nlist = nlist + self.descrpt = descrpt + inputs_reshape = tf.reshape (descrpt, [-1, self.ndescrpt]) + atom_ener = self._net (inputs_reshape, name, reuse = reuse) + atom_ener_reshape = tf.reshape(atom_ener, [-1, self.natoms[0]]) + energy = tf.reduce_sum (atom_ener_reshape, axis = 1) + net_deriv_ = tf.gradients (atom_ener, inputs_reshape) + net_deriv = net_deriv_[0] + net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) + + force = op_module.prod_force (net_deriv_reshape, + descrpt_deriv, + nlist, + axis, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + virial, atom_vir = op_module.prod_virial (net_deriv_reshape, + descrpt_deriv, + rij, + nlist, + axis, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + return energy, force, virial + + + def comp_f_dw (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + energy, force, virial = self.comp_ef (dcoord, dbox, dtype, tnatoms, name, reuse) + with tf.variable_scope(name, reuse=True): + net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) + f_mag = tf.reduce_sum (tf.nn.tanh(force)) + f_mag_dw = tf.gradients (f_mag, net_w) + assert (len(f_mag_dw) == 1), "length of dw is wrong" + return f_mag, f_mag_dw[0] + + + def comp_v_dw (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + energy, force, virial = self.comp_ef (dcoord, dbox, dtype, tnatoms, name, reuse) + with tf.variable_scope(name, reuse=True): + net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) + v_mag = tf.reduce_sum (virial) + v_mag_dw = tf.gradients (v_mag, net_w) + assert (len(v_mag_dw) == 1), "length of dw is wrong" + return v_mag, v_mag_dw[0] + + + +class TestNonSmooth(Inter, unittest.TestCase): + def __init__ (self, *args, **kwargs): + self.places = 5 + data = Data() + Inter.__init__(self, data) + unittest.TestCase.__init__(self, *args, **kwargs) + self.controller = object() + + def test_force (self) : + force_test(self, self, places=5) + # t_energy, t_force, t_virial \ + # = self.comp_ef (self.coord, self.box, self.type, self.tnatoms, name = "test") + # self.sess.run (tf.global_variables_initializer()) + # dcoord, dbox, dtype = self.data.get_data () + # hh = 1e-6 + # dcoordp = np.copy(dcoord) + # dcoordm = np.copy(dcoord) + # dcoordp[0,0] = dcoord[0,0] + hh + # dcoordm[0,0] = dcoord[0,0] - hh + # [axis0, nlist0, d0] = self.sess.run ([self.axis, self.nlist, self.descrpt], + # feed_dict = { + # self.coord: dcoordp, + # self.box: dbox, + # self.type: dtype, + # self.tnatoms: self.natoms} + # ) + # [axis1, nlist1, d1] = self.sess.run ([self.axis, self.nlist, self.descrpt], + # feed_dict = { + # self.coord: dcoordm, + # self.box: dbox, + # self.type: dtype, + # self.tnatoms: self.natoms} + # ) + # print((nlist0 - nlist1)) + # print((axis0 - axis1)) + + def test_virial (self) : + virial_test(self, self, places=5) + + def test_force_dw (self) : + force_dw_test(self, self, places=5) + + def test_virial_dw (self) : + virial_dw_test(self, self, places=5) + + +if __name__ == '__main__': + unittest.main() diff --git a/source/tests/test_descrpt_smooth.py b/source/tests/test_descrpt_smooth.py new file mode 100644 index 0000000000..556baaec8a --- /dev/null +++ b/source/tests/test_descrpt_smooth.py @@ -0,0 +1,174 @@ +import os,sys +import numpy as np +import tensorflow as tf +import unittest + +from tensorflow.python.framework import ops + +# load force module +module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") +so_file = os.path.join(module_path, "libop_abi.so") +assert (os.path.isfile ( so_file )), "op module does not exist" +op_module = tf.load_op_library( so_file ) + +# load grad of force module +sys.path.append (module_path) +import _prod_force_grad +import _prod_virial_grad +import _prod_force_norot_grad +import _prod_virial_norot_grad +import _soft_min_force_grad +import _soft_min_virial_grad + +from common import force_test +from common import virial_test +from common import force_dw_test +from common import virial_dw_test +from common import Data + + +class Inter(): + def __init__ (self, + data) : + self.sess = tf.Session() + self.data = data + self.natoms = self.data.get_natoms() + self.ntypes = self.data.get_ntypes() + self.sel_a = [12,24] + self.sel_r = [0,0] + self.rcut_a = -1 + self.rcut_r_smth = 2.45 + self.rcut_r = 10.0 + self.nnei_a = np.cumsum(self.sel_a)[-1] + self.nnei_r = np.cumsum(self.sel_r)[-1] + self.nnei = self.nnei_a + self.nnei_r + self.ndescrpt_a = self.nnei_a * 4 + self.ndescrpt_r = self.nnei_r * 1 + self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r + davg = np.zeros ([self.ntypes, self.ndescrpt]) + dstd = np.ones ([self.ntypes, self.ndescrpt]) + self.t_avg = tf.constant(davg.astype(np.float64)) + self.t_std = tf.constant(dstd.astype(np.float64)) + self.default_mesh = np.zeros (6, dtype = np.int32) + self.default_mesh[3] = 2 + self.default_mesh[4] = 2 + self.default_mesh[5] = 2 + # make place holder + self.coord = tf.placeholder(tf.float64, [None, self.natoms[0] * 3], name='t_coord') + self.box = tf.placeholder(tf.float64, [None, 9], name='t_box') + self.type = tf.placeholder(tf.int32, [None, self.natoms[0]], name = "t_type") + self.tnatoms = tf.placeholder(tf.int32, [None], name = "t_natoms") + + def _net (self, + inputs, + name, + reuse = False) : + with tf.variable_scope(name, reuse=reuse): + net_w = tf.get_variable ('net_w', + [self.ndescrpt], + tf.float64, + tf.constant_initializer (self.net_w_i)) + dot_v = tf.matmul (tf.reshape (inputs, [-1, self.ndescrpt]), + tf.reshape (net_w, [self.ndescrpt, 1])) + return tf.reshape (dot_v, [-1]) + + def comp_ef (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + descrpt, descrpt_deriv, rij, nlist \ + = op_module.descrpt_norot (dcoord, + dtype, + tnatoms, + dbox, + tf.constant(self.default_mesh), + self.t_avg, + self.t_std, + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + rcut_r_smth = self.rcut_r_smth, + sel_a = self.sel_a, + sel_r = self.sel_r) + inputs_reshape = tf.reshape (descrpt, [-1, self.ndescrpt]) + atom_ener = self._net (inputs_reshape, name, reuse = reuse) + atom_ener_reshape = tf.reshape(atom_ener, [-1, self.natoms[0]]) + energy = tf.reduce_sum (atom_ener_reshape, axis = 1) + net_deriv_ = tf.gradients (atom_ener, inputs_reshape) + net_deriv = net_deriv_[0] + net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) + + force = op_module.prod_force_norot (net_deriv_reshape, + descrpt_deriv, + nlist, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + virial, atom_vir = op_module.prod_virial_norot (net_deriv_reshape, + descrpt_deriv, + rij, + nlist, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + return energy, force, virial + + + def comp_f_dw (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + energy, force, virial = self.comp_ef (dcoord, dbox, dtype, tnatoms, name, reuse) + with tf.variable_scope(name, reuse=True): + net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) + f_mag = tf.reduce_sum (tf.nn.tanh(force)) + f_mag_dw = tf.gradients (f_mag, net_w) + assert (len(f_mag_dw) == 1), "length of dw is wrong" + return f_mag, f_mag_dw[0] + + + def comp_v_dw (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + energy, force, virial = self.comp_ef (dcoord, dbox, dtype, tnatoms, name, reuse) + with tf.variable_scope(name, reuse=True): + net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) + v_mag = tf.reduce_sum (virial) + v_mag_dw = tf.gradients (v_mag, net_w) + assert (len(v_mag_dw) == 1), "length of dw is wrong" + return v_mag, v_mag_dw[0] + + + +class TestSmooth(Inter, unittest.TestCase): + def __init__ (self, *args, **kwargs): + self.places = 5 + data = Data() + Inter.__init__(self, data) + unittest.TestCase.__init__(self, *args, **kwargs) + self.controller = object() + + def test_force (self) : + force_test(self, self, places=5, suffix = '_smth') + + def test_virial (self) : + virial_test(self, self, places=5, suffix = '_smth') + + def test_force_dw (self) : + force_dw_test(self, self, places=5, suffix = '_smth') + + def test_virial_dw (self) : + virial_dw_test(self, self, places=5, suffix = '_smth') + + +if __name__ == '__main__': + unittest.main() diff --git a/source/tests/test_tab_nonsmth.py b/source/tests/test_tab_nonsmth.py new file mode 100644 index 0000000000..12de7aaebb --- /dev/null +++ b/source/tests/test_tab_nonsmth.py @@ -0,0 +1,183 @@ +import os,sys +import numpy as np +import tensorflow as tf +import unittest + +from tensorflow.python.framework import ops + +# load force module +module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") +so_file = os.path.join(module_path, "libop_abi.so") +assert (os.path.isfile ( so_file )), "op module does not exist" +op_module = tf.load_op_library( so_file ) + +# load grad of force module +sys.path.append (module_path) +import _prod_force_grad +import _prod_virial_grad +import _prod_force_norot_grad +import _prod_virial_norot_grad +import _soft_min_force_grad +import _soft_min_virial_grad +from TabInter import TabInter + +from common import force_test +from common import virial_test +from common import force_dw_test +from common import virial_dw_test +from common import Data +from test_descrpt_nonsmth import Inter + + +def _make_tab(ntype) : + xx = np.arange(0,9,0.001) + yy = 1000/(xx+.5)**6 + prt = xx + ninter = ntype * (ntype + 1) // 2 + for ii in range(ninter) : + prt = np.append(prt, yy) + prt = np.reshape(prt, [ninter+1, -1]) + np.savetxt('tab.xvg', prt.T) + + +class IntplInter(Inter): + def __init__ (self, + data) : + # tabulated + Inter.__init__(self, data) + _make_tab(data.get_ntypes()) + self.srtab = TabInter('tab.xvg') + self.smin_alpha = 0.3 + self.sw_rmin = 1 + self.sw_rmax = 3.45 + tab_info, tab_data = self.srtab.get() + with tf.variable_scope('tab', reuse=tf.AUTO_REUSE): + self.tab_info = tf.get_variable('t_tab_info', + tab_info.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) + self.tab_data = tf.get_variable('t_tab_data', + tab_data.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) + + def comp_interpl_ef (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + descrpt, descrpt_deriv, rij, nlist, axis \ + = op_module.descrpt (dcoord, + dtype, + tnatoms, + dbox, + tf.constant(self.default_mesh), + self.t_avg, + self.t_std, + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + sel_a = self.sel_a, + sel_r = self.sel_r, + axis_rule = self.axis_rule) + inputs_reshape = tf.reshape (descrpt, [-1, self.ndescrpt]) + atom_ener = self._net (inputs_reshape, name, reuse = reuse) + + sw_lambda, sw_deriv \ + = op_module.soft_min_switch(dtype, + rij, + nlist, + tnatoms, + sel_a = self.sel_a, + sel_r = self.sel_r, + alpha = self.smin_alpha, + rmin = self.sw_rmin, + rmax = self.sw_rmax) + inv_sw_lambda = 1.0 - sw_lambda + tab_atom_ener, tab_force, tab_atom_virial \ + = op_module.tab_inter(self.tab_info, + self.tab_data, + dtype, + rij, + nlist, + tnatoms, + sw_lambda, + sel_a = self.sel_a, + sel_r = self.sel_r) + energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, self.natoms[0]]) + tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) + atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener + energy_raw = tab_atom_ener + atom_ener + + energy_raw = tf.reshape(energy_raw, [-1, self.natoms[0]]) + energy = tf.reduce_sum (energy_raw, axis = 1) + + net_deriv_ = tf.gradients (atom_ener, inputs_reshape) + net_deriv = net_deriv_[0] + net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) + + force = op_module.prod_force (net_deriv_reshape, + descrpt_deriv, + nlist, + axis, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + sw_force \ + = op_module.soft_min_force(energy_diff, + sw_deriv, + nlist, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + force = force + sw_force + tab_force + virial, atom_vir = op_module.prod_virial (net_deriv_reshape, + descrpt_deriv, + rij, + nlist, + axis, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + sw_virial, sw_atom_virial \ + = op_module.soft_min_virial (energy_diff, + sw_deriv, + rij, + nlist, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + # atom_virial = atom_virial + sw_atom_virial + tab_atom_virial + virial = virial + sw_virial \ + + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, self.natoms[1], 9]), axis = 1) + + return energy, force, virial + + + +class TestTabNonSmooth(IntplInter, unittest.TestCase): + def __init__ (self, *args, **kwargs): + self.places = 5 + data = Data() + IntplInter.__init__(self, data) + unittest.TestCase.__init__(self, *args, **kwargs) + self.controller = object() + + def test_force (self) : + force_test(self, self, places=5, suffix = '_tab') + + def test_virial (self) : + virial_test(self, self, places=5, suffix = '_tab') + + def test_force_dw (self) : + force_dw_test(self, self, places=5, suffix = '_tab') + + def test_virial_dw (self) : + virial_dw_test(self, self, places=5, suffix = '_tab') + + +if __name__ == '__main__': + unittest.main() diff --git a/source/tests/test_tab_smooth.py b/source/tests/test_tab_smooth.py new file mode 100644 index 0000000000..856a25c7ad --- /dev/null +++ b/source/tests/test_tab_smooth.py @@ -0,0 +1,181 @@ +import os,sys +import numpy as np +import tensorflow as tf +import unittest + +from tensorflow.python.framework import ops + +# load force module +module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") +so_file = os.path.join(module_path, "libop_abi.so") +assert (os.path.isfile ( so_file )), "op module does not exist" +op_module = tf.load_op_library( so_file ) + +# load grad of force module +sys.path.append (module_path) +import _prod_force_grad +import _prod_virial_grad +import _prod_force_norot_grad +import _prod_virial_norot_grad +import _soft_min_force_grad +import _soft_min_virial_grad +from TabInter import TabInter + +from common import force_test +from common import virial_test +from common import force_dw_test +from common import virial_dw_test +from common import Data +from test_descrpt_smooth import Inter + + +def _make_tab(ntype) : + xx = np.arange(0,9,0.001) + yy = 1000/(xx+.5)**6 + prt = xx + ninter = ntype * (ntype + 1) // 2 + for ii in range(ninter) : + prt = np.append(prt, yy) + prt = np.reshape(prt, [ninter+1, -1]) + np.savetxt('tab.xvg', prt.T) + + +class IntplInter(Inter): + def __init__ (self, + data) : + # tabulated + Inter.__init__(self, data) + _make_tab(data.get_ntypes()) + self.srtab = TabInter('tab.xvg') + self.smin_alpha = 0.3 + self.sw_rmin = 1 + self.sw_rmax = 3.45 + tab_info, tab_data = self.srtab.get() + with tf.variable_scope('tab', reuse=tf.AUTO_REUSE): + self.tab_info = tf.get_variable('t_tab_info', + tab_info.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) + self.tab_data = tf.get_variable('t_tab_data', + tab_data.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) + + def comp_ef (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + descrpt, descrpt_deriv, rij, nlist \ + = op_module.descrpt_norot (dcoord, + dtype, + tnatoms, + dbox, + tf.constant(self.default_mesh), + self.t_avg, + self.t_std, + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + rcut_r_smth = self.rcut_r_smth, + sel_a = self.sel_a, + sel_r = self.sel_r) + inputs_reshape = tf.reshape (descrpt, [-1, self.ndescrpt]) + atom_ener = self._net (inputs_reshape, name, reuse = reuse) + + sw_lambda, sw_deriv \ + = op_module.soft_min_switch(dtype, + rij, + nlist, + tnatoms, + sel_a = self.sel_a, + sel_r = self.sel_r, + alpha = self.smin_alpha, + rmin = self.sw_rmin, + rmax = self.sw_rmax) + inv_sw_lambda = 1.0 - sw_lambda + tab_atom_ener, tab_force, tab_atom_virial \ + = op_module.tab_inter(self.tab_info, + self.tab_data, + dtype, + rij, + nlist, + tnatoms, + sw_lambda, + sel_a = self.sel_a, + sel_r = self.sel_r) + energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, self.natoms[0]]) + tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) + atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener + energy_raw = tab_atom_ener + atom_ener + + energy_raw = tf.reshape(energy_raw, [-1, self.natoms[0]]) + energy = tf.reduce_sum (energy_raw, axis = 1) + + net_deriv_ = tf.gradients (atom_ener, inputs_reshape) + net_deriv = net_deriv_[0] + net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) + + force = op_module.prod_force_norot (net_deriv_reshape, + descrpt_deriv, + nlist, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + sw_force \ + = op_module.soft_min_force(energy_diff, + sw_deriv, + nlist, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + force = force + sw_force + tab_force + virial, atom_vir = op_module.prod_virial_norot (net_deriv_reshape, + descrpt_deriv, + rij, + nlist, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + sw_virial, sw_atom_virial \ + = op_module.soft_min_virial (energy_diff, + sw_deriv, + rij, + nlist, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + # atom_virial = atom_virial + sw_atom_virial + tab_atom_virial + virial = virial + sw_virial \ + + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, self.natoms[1], 9]), axis = 1) + + return energy, force, virial + + + +class TestTabSmooth(IntplInter, unittest.TestCase): + def __init__ (self, *args, **kwargs): + self.places = 5 + data = Data() + IntplInter.__init__(self, data) + unittest.TestCase.__init__(self, *args, **kwargs) + self.controller = object() + + def test_force (self) : + force_test(self, self, places=5, suffix = '_tab_smth') + + def test_virial (self) : + virial_test(self, self, places=5, suffix = '_tab_smth') + + def test_force_dw (self) : + force_dw_test(self, self, places=5, suffix = '_tab_smth') + + def test_virial_dw (self) : + virial_dw_test(self, self, places=5, suffix = '_tab_smth') + + +if __name__ == '__main__': + unittest.main() diff --git a/source/train/CMakeLists.txt b/source/train/CMakeLists.txt index f56070e65e..0a9d813fd5 100644 --- a/source/train/CMakeLists.txt +++ b/source/train/CMakeLists.txt @@ -2,25 +2,19 @@ configure_file("RunOptions.py.in" "${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py" @ONLY) -file(GLOB LIB_PY Data.py DataSystem.py Model.py Test.py TestNorot.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) +file(GLOB LIB_PY DeepPot.py Data.py DataSystem.py Model.py Test.py TestNorot.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) file(GLOB CLS_PY Local.py Slurm.py) install( FILES ${LIB_PY} - DESTINATION lib/deepmd + DESTINATION deepmd ) install( FILES ${CLS_PY} - DESTINATION lib/deepmd/cluster + DESTINATION deepmd/cluster ) install( - PROGRAMS train.py - DESTINATION bin/ - RENAME dp_train -) -install( - PROGRAMS test.py - DESTINATION bin/ - RENAME dp_test + FILES __main__.py __init__.py train.py test.py + DESTINATION deepmd ) diff --git a/source/train/Data.py b/source/train/Data.py index 2585c1bde4..7012ed16f2 100644 --- a/source/train/Data.py +++ b/source/train/Data.py @@ -12,23 +12,36 @@ class DataSets (object): def __init__ (self, sys_path, set_prefix, - seed = None) : + seed = None, + shuffle_test = True) : self.dirs = glob.glob (os.path.join(sys_path, set_prefix + ".*")) self.dirs.sort() # load atom type self.atom_type, self.idx_map, self.idx3_map = self.load_type (sys_path) + # load atom type map + self.type_map = self.load_type_map(sys_path) + if self.type_map is not None: + assert(len(self.type_map) >= max(self.atom_type)+1) # train dirs self.test_dir = self.dirs[-1] if len(self.dirs) == 1 : self.train_dirs = self.dirs else : self.train_dirs = self.dirs[:-1] + # check fparam + has_fparam = [ os.path.isfile(os.path.join(ii, 'fparam.npy')) for ii in self.dirs ] + if any(has_fparam) and (not all(has_fparam)) : + raise RuntimeError("system %s: if any set has frame parameter, then all sets should have frame parameter" % sys_path) + if all(has_fparam) : + self.has_fparam = 0 + else : + self.has_fparam = -1 # energy norm self.eavg = self.stats_energy() # load sets self.set_count = 0 self.load_batch_set (self.train_dirs[self.set_count % self.get_numb_set()]) - self.load_test_set (self.test_dir) + self.load_test_set (self.test_dir, shuffle_test) def check_batch_size (self, batch_size) : for ii in self.train_dirs : @@ -56,6 +69,17 @@ def load_type (self, sys_path) : idx3_map = np.lexsort ((idx3, atom_type3)) return atom_type, idx_map, idx3_map + def load_type_map(self, sys_path) : + fname = os.path.join(sys_path, 'type_map.raw') + if os.path.isfile(fname) : + with open(os.path.join(sys_path, 'type_map.raw')) as fp: + return fp.read().split() + else : + return None + + def get_type_map(self) : + return self.type_map + def get_numb_set (self) : return len (self.train_dirs) @@ -138,6 +162,13 @@ def load_batch_set (self, nframe = self.box_batch.shape[0] self.coord_batch = np.reshape(self.coord_batch, [nframe, -1]) ncoord = self.coord_batch.shape[1] + if self.has_fparam >= 0: + self.fparam_batch = np.load(os.path.join(set_name, 'fparam.npy')) + self.fparam_batch = np.reshape(self.fparam_batch, [nframe, -1]) + if self.has_fparam == 0 : + self.has_fparam = self.fparam_batch.shape[1] + else : + assert self.has_fparam == self.fparam_batch.shape[1] self.prop_c_batch = np.zeros (4) self.prop_c_batch[0], self.energy_batch, self.prop_c_batch[3], self.atom_ener_batch \ = self.load_energy (nframe, ncoord // 3, @@ -160,6 +191,8 @@ def load_batch_set (self, self.coord_batch = self.coord_batch[idx] self.box_batch = self.box_batch[idx] self.type_batch = np.tile (self.atom_type, (nframe, 1)) + if self.has_fparam >= 0 : + self.fparam_batch = self.fparam_batch[idx] self.reset_iter () # sort according to type self.type_batch = self.type_batch[:, self.idx_map] @@ -169,7 +202,8 @@ def load_batch_set (self, end_time = time.time() def load_test_set (self, - set_name) : + set_name, + shuffle_test) : start_time = time.time() self.coord_test = np.load(os.path.join(set_name, "coord.npy")) self.box_test = np.load(os.path.join(set_name, "box.npy")) @@ -177,6 +211,14 @@ def load_test_set (self, nframe = self.box_test.shape[0] self.coord_test = np.reshape(self.coord_test, [nframe, -1]) ncoord = self.coord_test.shape[1] + fparam_file = os.path.join(set_name, 'fparam.npy') + if self.has_fparam >= 0 : + self.fparam_test = np.load(fparam_file) + self.fparam_test = np.reshape(self.fparam_test, [nframe, -1]) + if self.has_fparam == 0 : + self.has_fparam = self.fparam_test.shape[1] + else : + assert self.has_fparam == self.fparam_test.shape[1] self.prop_c_test = np.zeros (4) self.prop_c_test[0], self.energy_test, self.prop_c_test[3], self.atom_ener_test \ = self.load_energy (nframe, ncoord // 3, @@ -191,7 +233,8 @@ def load_test_set (self, os.path.join(set_name, "virial.npy")) # shuffle data idx = np.arange (nframe) - np.random.shuffle (idx) + if shuffle_test: + np.random.shuffle (idx) self.energy_test = self.energy_test[idx] self.force_test = self.force_test[idx] self.virial_test = self.virial_test[idx] @@ -199,6 +242,8 @@ def load_test_set (self, self.coord_test = self.coord_test[idx] self.box_test = self.box_test[idx] self.type_test = np.tile (self.atom_type, (nframe, 1)) + if self.has_fparam >= 0 : + self.fparam_test = self.fparam_test[idx] # sort according to type self.type_test = self.type_test[:, self.idx_map] self.atom_ener_test = self.atom_ener_test[:, self.idx_map] @@ -215,6 +260,10 @@ def get_test (self) : returned property prefector [4] in order: energy, force, virial, atom_ener """ + if self.has_fparam >= 0 : + ret_fparam = self.fparam_test.astype(global_np_float_precision) + else : + ret_fparam = None return \ self.prop_c_test.astype(np.float32), \ self.energy_test.astype(global_np_float_precision), \ @@ -223,7 +272,8 @@ def get_test (self) : self.atom_ener_test.astype(global_np_float_precision), \ self.coord_test.astype(global_np_float_precision), \ self.box_test.astype(global_np_float_precision), \ - self.type_test + self.type_test,\ + ret_fparam def get_batch (self, batch_size) : @@ -242,6 +292,10 @@ def get_batch (self, iterator_1 = set_size idx = np.arange (self.iterator, iterator_1) self.iterator += batch_size + if self.has_fparam >= 0 : + ret_fparam = self.fparam_batch[idx, :].astype(global_np_float_precision) + else : + ret_fparam = None return \ self.prop_c_batch.astype(np.float32), \ self.energy_batch[idx].astype(global_np_float_precision), \ @@ -250,7 +304,8 @@ def get_batch (self, self.atom_ener_batch[idx, :].astype(global_np_float_precision), \ self.coord_batch[idx, :].astype(global_np_float_precision), \ self.box_batch[idx, :].astype(global_np_float_precision), \ - self.type_batch[idx, :] + self.type_batch[idx, :],\ + ret_fparam def get_natoms (self) : sample_type = self.type_batch[0] @@ -281,14 +336,6 @@ def get_sys_numb_batch (self, batch_size) : def get_ener (self) : return self.eavg -if __name__ == '__main__': - data = DataSets (".", "set") - prop_c, energy, force, virial, atom_ener, coord, box, ttype = data.get_batch(1) - print (energy.shape) - print (force.shape) - print (coord.shape) - print (box.shape) - print (ttype.shape) - # energy, force, coord, box, ttype = data.get_test() - print (energy) - + def numb_fparam(self) : + return self.has_fparam + diff --git a/source/train/DataSystem.py b/source/train/DataSystem.py index 19bc4cf4dc..33a81b5404 100644 --- a/source/train/DataSystem.py +++ b/source/train/DataSystem.py @@ -32,19 +32,31 @@ def __init__ (self, sys_all_types = np.loadtxt(os.path.join(ii, "type.raw")).astype(int) self.ntypes.append(np.max(sys_all_types) + 1) self.sys_ntypes = max(self.ntypes) + type_map = [] for ii in range(self.nsystems) : self.natoms.append(self.data_systems[ii].get_natoms()) self.natoms_vec.append(self.data_systems[ii].get_natoms_vec(self.sys_ntypes).astype(int)) self.nbatches.append(self.data_systems[ii].get_sys_numb_batch(self.batch_size[ii])) + type_map.append(self.data_systems[ii].get_type_map()) + self.type_map = self.check_type_map_consistency(type_map) + + # check frame parameters + has_fparam = [ii.numb_fparam() for ii in self.data_systems] + for ii in has_fparam : + if ii != has_fparam[0] : + raise RuntimeError("if any system has frame parameter, then all systems should have the same number of frame parameter") + self.has_fparam = has_fparam[0] # check the size of data if they satisfy the requirement of batch and test for ii in range(self.nsystems) : chk_ret = self.data_systems[ii].check_batch_size(self.batch_size[ii]) if chk_ret is not None : - raise RuntimeError(" required batch size %d is larger than the size %d of the dataset %s" % (self.batch_size[ii], chk_ret[1], chk_ret[0])) + raise RuntimeError ("system %s required batch size %d is larger than the size %d of the dataset %s" % \ + (self.system_dirs[ii], self.batch_size[ii], chk_ret[1], chk_ret[0])) chk_ret = self.data_systems[ii].check_test_size(test_size) if chk_ret is not None : - raise RuntimeError(" required test size %d is larger than the size %d of the dataset %s" % (test_size, chk_ret[1], chk_ret[0])) + print("WARNNING: system %s required test size %d is larger than the size %d of the dataset %s" % \ + (self.system_dirs[ii], test_size, chk_ret[1], chk_ret[0])) if run_opt is not None: self.print_summary(run_opt) @@ -59,9 +71,10 @@ def __init__ (self, self.test_coord = [] self.test_box = [] self.test_type = [] + self.test_fparam = [] self.default_mesh = [] for ii in range(self.nsystems) : - test_prop_c, test_energy, test_force, test_virial, test_atom_ener, test_coord, test_box, test_type \ + test_prop_c, test_energy, test_force, test_virial, test_atom_ener, test_coord, test_box, test_type, test_fparam \ = self.data_systems[ii].get_test () self.test_prop_c.append(test_prop_c) self.test_energy.append(test_energy) @@ -71,6 +84,7 @@ def __init__ (self, self.test_coord.append(test_coord) self.test_box.append(test_box) self.test_type.append(test_type) + self.test_fparam.append(test_fparam) ncell = np.ones (3, dtype=np.int32) cell_size = np.max (rcut) avg_box = np.average (test_box, axis = 0) @@ -85,6 +99,24 @@ def __init__ (self, self.default_mesh.append(default_mesh) self.pick_idx = 0 + + def check_type_map_consistency(self, type_map_list): + ret = [] + for ii in type_map_list: + if ii is not None: + min_len = min([len(ii), len(ret)]) + for idx in range(min_len) : + if ii[idx] != ret[idx] : + raise RuntimeError('inconsistent type map: %s %s' % (str(ret), str(ii))) + if len(ii) > len(ret) : + ret = ii + return ret + + + def get_type_map(self): + return self.type_map + + def format_name_length(self, name, width) : if len(name) <= width: return '{: >{}}'.format(name, width) @@ -150,12 +182,12 @@ def get_batch (self, else : prob = self.process_sys_weights(sys_weights) self.pick_idx = np.random.choice(np.arange(self.nsystems), p = prob) - b_prop_c, b_energy, b_force, b_virial, b_atom_ener, b_coord, b_box, b_type \ + b_prop_c, b_energy, b_force, b_virial, b_atom_ener, b_coord, b_box, b_type, b_fparam \ = self.data_systems[self.pick_idx].get_batch(self.batch_size[self.pick_idx]) return \ b_prop_c, \ b_energy, b_force, b_virial, b_atom_ener, \ - b_coord, b_box, b_type, \ + b_coord, b_box, b_type, b_fparam, \ self.natoms_vec[self.pick_idx], \ self.default_mesh[self.pick_idx] @@ -175,6 +207,7 @@ def get_test (self, self.test_coord[idx], \ self.test_box[idx], \ self.test_type[idx], \ + self.test_fparam[idx], \ self.natoms_vec[idx], \ self.default_mesh[idx] @@ -193,6 +226,9 @@ def get_sys (self, idx) : def get_batch_size(self) : return self.batch_size + def numb_fparam(self) : + return self.has_fparam + def _main () : sys = ['/home/wanghan/study/deep.md/results.01/data/mos2/only_raws/20', '/home/wanghan/study/deep.md/results.01/data/mos2/only_raws/30', diff --git a/source/train/DeepPot.py b/source/train/DeepPot.py new file mode 100644 index 0000000000..c9f93d5823 --- /dev/null +++ b/source/train/DeepPot.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 + +import os,sys +import numpy as np +import tensorflow as tf + +from tensorflow.python.framework import ops +module_path = os.path.dirname(os.path.realpath(__file__)) +assert (os.path.isfile (os.path.join(module_path, "libop_abi.so"))), "op module does not exist" +op_module = tf.load_op_library(os.path.join(module_path, "libop_abi.so")) + +def _load_graph(frozen_graph_filename, + prefix = 'load'): + # We load the protobuf file from the disk and parse it to retrieve the + # unserialized graph_def + with tf.gfile.GFile(frozen_graph_filename, "rb") as f: + graph_def = tf.GraphDef() + graph_def.ParseFromString(f.read()) + + # Then, we can use again a convenient built-in function to import a graph_def into the + # current default Graph + with tf.Graph().as_default() as graph: + tf.import_graph_def( + graph_def, + input_map=None, + return_elements=None, + name=prefix, + producer_op_list=None + ) + return graph + + +def _rep_int (s): + try: + int(s) + return True + except ValueError: + return False + + +def _make_default_mesh(test_box) : + ncell = np.ones (3, dtype=np.int32) + avg_box = np.average (test_box, axis = 0) + cell_size = 3 + avg_box = np.reshape (avg_box, [3,3]) + for ii in range (3) : + ncell[ii] = int ( np.linalg.norm(avg_box[ii]) / cell_size ) + if (ncell[ii] < 2) : ncell[ii] = 2 + default_mesh = np.zeros (6, dtype = np.int32) + default_mesh[3] = ncell[0] + default_mesh[4] = ncell[1] + default_mesh[5] = ncell[2] + return default_mesh + + +class DeepPot () : + def __init__(self, + model_file) : + self.model_file = model_file + self.graph = _load_graph (self.model_file) + # checkout input/output tensors from graph + self.t_ntypes = self.graph.get_tensor_by_name ('load/t_ntypes:0') + self.t_rcut = self.graph.get_tensor_by_name ('load/t_rcut:0') + self.t_dfparam= self.graph.get_tensor_by_name ('load/t_dfparam:0') + self.t_tmap = self.graph.get_tensor_by_name ('load/t_tmap:0') + + self.t_coord = self.graph.get_tensor_by_name ('load/t_coord:0') + self.t_type = self.graph.get_tensor_by_name ('load/t_type:0') + self.t_natoms = self.graph.get_tensor_by_name ('load/t_natoms:0') + self.t_box = self.graph.get_tensor_by_name ('load/t_box:0') + self.t_mesh = self.graph.get_tensor_by_name ('load/t_mesh:0') + self.t_energy = self.graph.get_tensor_by_name ('load/energy_test:0') + self.t_force = self.graph.get_tensor_by_name ('load/force_test:0') + self.t_virial = self.graph.get_tensor_by_name ('load/virial_test:0') + self.t_ae = self.graph.get_tensor_by_name ('load/atom_energy_test:0') + self.t_av = self.graph.get_tensor_by_name ('load/atom_virial_test:0') + self.t_fparam = None + # check if the graph has fparam + for op in self.graph.get_operations(): + if op.name == 'load/t_fparam' : + self.t_fparam = self.graph.get_tensor_by_name ('load/t_fparam:0') + self.has_fparam = self.t_fparam is not None + # start a tf session associated to the graph + self.sess = tf.Session (graph = self.graph) + [self.ntypes, self.rcut, self.dfparam, self.tmap] = self.sess.run([self.t_ntypes, self.t_rcut, self.t_dfparam, self.t_tmap]) + self.tmap = self.tmap.decode('UTF-8').split() + + + def get_ntypes(self) : + return self.ntypes + + def get_rcut(self) : + return self.rcut + + def get_dim_fparam(self) : + return self.dfparam + + def get_type_map(self): + return self.tmap + + + def eval(self, + coords, + cells, + atom_types, + fparam = None, + atomic = False) : + # standarize the shape of inputs + coords = np.array(coords) + cells = np.array(cells) + atom_types = np.array(atom_types, dtype = int) + if coords.ndim == 1: + coords = np.array([coords]) + if cells.ndim == 1: + cells = np.array([cells]) + + # sort inputs + coords, atom_types, imap = self._sort_input(coords, atom_types) + + # check nframe + nframes = cells.shape[0] + assert(nframes == coords.shape[0]) + + # make natoms_vec and default_mesh + natoms_vec = self._make_natoms_vec(atom_types) + default_mesh = _make_default_mesh(cells) + + if self.has_fparam : + assert(fparam is not None) + + # evaluate + energy = [] + force = [] + virial = [] + ae = [] + av = [] + feed_dict_test = {} + feed_dict_test[self.t_natoms] = natoms_vec + feed_dict_test[self.t_mesh ] = default_mesh + feed_dict_test[self.t_type ] = atom_types + t_out = [self.t_energy, + self.t_force, + self.t_virial] + if atomic : + t_out += [self.t_ae, + self.t_av] + for ii in range(nframes) : + feed_dict_test[self.t_coord] = np.reshape(coords[ii:ii+1, :], [-1]) + feed_dict_test[self.t_box ] = cells[ii:ii+1, :] + if self.has_fparam: + feed_dict_test[self.t_fparam] = np.reshape(fparam[ii:ii+1, :], [-1]) + v_out = self.sess.run (t_out, feed_dict = feed_dict_test) + energy.append(v_out[0]) + force .append(v_out[1]) + virial.append(v_out[2]) + if atomic: + ae.append(v_out[3]) + av.append(v_out[4]) + + # reverse map of the outputs + force = self._reverse_map(np.reshape(force, [nframes,-1,3]), imap) + if atomic : + ae = self._reverse_map(np.reshape(ae, [nframes,-1,1]), imap) + av = self._reverse_map(np.reshape(av, [nframes,-1,9]), imap) + + # standarize the shape of outputs + energy = np.reshape(energy, [nframes]) + force = np.reshape(force, [nframes, -1]) + virial = np.reshape(virial, [nframes, -1]) + if atomic : + ae = np.reshape(ae, [nframes, -1]) + av = np.reshape(av, [nframes, -1]) + + if atomic: + return energy, force, virial, ae, av + else : + return energy, force, virial + + + def _sort_input(self, coord, atom_type) : + natoms = atom_type.size + idx = np.arange (natoms) + idx_map = np.lexsort ((idx, atom_type)) + nframes = coord.shape[0] + coord = coord.reshape([nframes, -1, 3]) + coord = np.reshape(coord[:,idx_map,:], [nframes, -1]) + atom_type = atom_type[idx_map] + return coord, atom_type, idx_map + + + def _reverse_map(self, vec, imap): + ret = np.zeros(vec.shape) + for idx,ii in enumerate(imap) : + ret[:,ii,:] = vec[:,idx,:] + return ret + + + def _make_natoms_vec(self, atom_types) : + natoms_vec = np.zeros (self.ntypes+2).astype(int) + natoms = atom_types.size + natoms_vec[0] = natoms + natoms_vec[1] = natoms + for ii in range (self.ntypes) : + natoms_vec[ii+2] = np.count_nonzero(atom_types == ii) + return natoms_vec + diff --git a/source/train/Model.py b/source/train/Model.py index 1294b5dcd6..fdef14b20f 100644 --- a/source/train/Model.py +++ b/source/train/Model.py @@ -26,7 +26,10 @@ import deepmd._prod_virial_grad import deepmd._prod_force_norot_grad import deepmd._prod_virial_norot_grad +import deepmd._soft_min_force_grad +import deepmd._soft_min_virial_grad from deepmd.RunOptions import RunOptions +from deepmd.TabInter import TabInter def j_must_have (jdata, key) : if not key in jdata.keys() : @@ -144,6 +147,14 @@ def _init_param(self, jdata): self.numb_test = j_must_have (jdata, 'numb_test') self.useBN = False + if 'use_srtab' in jdata : + self.srtab = TabInter(jdata['use_srtab']) + self.smin_alpha = j_must_have(jdata, 'smin_alpha') + self.sw_rmin = j_must_have(jdata, 'sw_rmin') + self.sw_rmax = j_must_have(jdata, 'sw_rmax') + else : + self.srtab = None + self.start_pref_e = j_must_have (jdata, 'start_pref_e') self.limit_pref_e = j_must_have (jdata, 'limit_pref_e') self.start_pref_f = j_must_have (jdata, 'start_pref_f') @@ -197,11 +208,22 @@ def build (self, self.batch_size = data.get_batch_size() + self.numb_fparam = data.numb_fparam() + if self.numb_fparam > 0 : + self._message("training with %d frame parameter(s)" % self.numb_fparam) + elif self.numb_fparam < 0 : + self._message("training without frame parameter") + else : + raise RuntimeError("number of frame parameter == 0") + + t_tmap = tf.constant(' '.join(data.get_type_map()), name = 't_tmap', dtype = tf.string) + davg, dstd, bias_e = self._data_stat(data) worker_device = "/job:%s/task:%d/%s" % (self.run_opt.my_job_name, self.run_opt.my_task_index, self.run_opt.my_device) + with tf.device(tf.train.replica_device_setter(worker_device = worker_device, cluster = self.run_opt.cluster_spec)): self._build_lr(lr) @@ -217,7 +239,7 @@ def _data_stat(self, data): for ii in range(data.get_nsystems()) : stat_prop_c, \ stat_energy, stat_force, stat_virial, start_atom_ener, \ - stat_coord, stat_box, stat_type, natoms_vec, default_mesh \ + stat_coord, stat_box, stat_type, stat_fparam, natoms_vec, default_mesh \ = data.get_batch (sys_idx = ii) natoms_vec = natoms_vec.astype(np.int32) all_stat_coord.append(stat_coord) @@ -266,6 +288,20 @@ def _build_network(self, davg, dstd, bias_atom_e): t_rcut = tf.constant(np.max([self.rcut_r, self.rcut_a]), name = 't_rcut', dtype = global_tf_float_precision) t_ntypes = tf.constant(self.ntypes, name = 't_ntypes', dtype = tf.int32) + t_dfparam = tf.constant(self.numb_fparam, name = 't_dfparam', dtype = tf.int32) + + if self.srtab is not None : + tab_info, tab_data = self.srtab.get() + self.tab_info = tf.get_variable('t_tab_info', + tab_info.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) + self.tab_data = tf.get_variable('t_tab_data', + tab_data.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) self.t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') self.t_energy = tf.placeholder(global_ener_float_precision, [None], name='t_energy') @@ -278,6 +314,10 @@ def _build_network(self, davg, dstd, bias_atom_e): self.t_box = tf.placeholder(global_tf_float_precision, [None, 9], name='t_box') self.t_mesh = tf.placeholder(tf.int32, [None], name='t_mesh') self.is_training = tf.placeholder(tf.bool) + if self.numb_fparam > 0 : + self.t_fparam = tf.placeholder(global_tf_float_precision, [None], name='t_fparam') + else : + self.t_fparam = None self.batch_size_value = list(set(self.batch_size)) self.batch_size_value.sort() @@ -289,7 +329,8 @@ def _build_network(self, davg, dstd, bias_atom_e): self.t_type, self.t_natoms, self.t_box, - self.t_mesh, + self.t_mesh, + self.t_fparam, bias_atom_e = bias_atom_e, suffix = "test", reuse = False) @@ -300,6 +341,7 @@ def _build_network(self, davg, dstd, bias_atom_e): self.t_natoms, self.t_box, self.t_mesh, + self.t_fparam, bias_atom_e = bias_atom_e, suffix = "train_test", reuse = True) @@ -315,6 +357,7 @@ def _build_network(self, davg, dstd, bias_atom_e): self.t_natoms, self.t_box, self.t_mesh, + self.t_fparam, bias_atom_e = bias_atom_e, suffix = "train_batch_" + str(self.batch_size_value[ii]), reuse = True) @@ -478,7 +521,7 @@ def train (self, while cur_batch < stop_batch : batch_prop_c, \ batch_energy, batch_force, batch_virial, batch_atom_ener, \ - batch_coord, batch_box, batch_type, \ + batch_coord, batch_box, batch_type, batch_fparam, \ natoms_vec, \ default_mesh \ = data.get_batch (sys_weights = self.sys_weights) @@ -495,6 +538,8 @@ def train (self, self.t_natoms: natoms_vec, self.t_mesh: default_mesh, self.is_training: True} + if self.numb_fparam > 0 : + feed_dict_batch[self.t_fparam] = np.reshape(batch_fparam, [-1]) if self.display_in_training and cur_batch == 0 : self.test_on_the_fly(fp, data, feed_dict_batch, cur_bs_idx) if self.timing_in_training : tic = time.time() @@ -553,7 +598,7 @@ def test_on_the_fly (self, ii) : test_prop_c, \ test_energy, test_force, test_virial, test_atom_ener, \ - test_coord, test_box, test_type, \ + test_coord, test_box, test_type, test_fparam, \ natoms_vec, \ default_mesh \ = data.get_test () @@ -568,6 +613,8 @@ def test_on_the_fly (self, self.t_natoms: natoms_vec, self.t_mesh: default_mesh, self.is_training: False} + if self.numb_fparam > 0 : + feed_dict_test[self.t_fparam] = np.reshape(test_fparam [:self.numb_test, :], [-1]) error_test, error_e_test, error_f_test, error_v_test, error_ae_test \ = self.sess.run([self.l2_l_tst, \ self.l2_el_tst, \ @@ -603,7 +650,7 @@ def test_on_the_fly (self, def compute_dstats_sys_smth (self, data_coord, data_box, - data_atype, + data_atype, natoms_vec, mesh, reuse = None) : @@ -847,7 +894,8 @@ def build_interaction (self, natoms, box, mesh, - suffix, + fparam, + suffix = 'inter', bias_atom_e = None, reuse = None): coord = tf.reshape (coord_, [-1, natoms[1] * 3]) @@ -884,9 +932,41 @@ def build_interaction (self, descrpt_reshape = tf.reshape(descrpt, [-1, self.ndescrpt]) - atom_ener = self.build_atom_net (nframes, descrpt_reshape, natoms, bias_atom_e = bias_atom_e, reuse = reuse) + atom_ener = self.build_atom_net (nframes, descrpt_reshape, fparam, natoms, bias_atom_e = bias_atom_e, reuse = reuse) + + if self.srtab is not None : + sw_lambda, sw_deriv \ + = op_module.soft_min_switch(atype, + rij, + nlist, + natoms, + sel_a = self.sel_a, + sel_r = self.sel_r, + alpha = self.smin_alpha, + rmin = self.sw_rmin, + rmax = self.sw_rmax) + inv_sw_lambda = 1.0 - sw_lambda + # NOTICE: + # atom energy is not scaled, + # force and virial are scaled + tab_atom_ener, tab_force, tab_atom_virial \ + = op_module.tab_inter(self.tab_info, + self.tab_data, + atype, + rij, + nlist, + natoms, + sw_lambda, + sel_a = self.sel_a, + sel_r = self.sel_r) + energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, natoms[0]]) + tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) + atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener + energy_raw = tab_atom_ener + atom_ener + else : + energy_raw = atom_ener - energy_raw = tf.reshape(atom_ener, [-1, natoms[0]], name = 'atom_energy_'+suffix) + energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'atom_energy_'+suffix) energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='energy_'+suffix) net_deriv_tmp = tf.gradients (atom_ener, descrpt_reshape) @@ -908,6 +988,16 @@ def build_interaction (self, natoms, n_a_sel = self.nnei_a, n_r_sel = self.nnei_r) + if self.srtab is not None : + sw_force \ + = op_module.soft_min_force(energy_diff, + sw_deriv, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + force = force + sw_force + tab_force + force = tf.reshape (force, [-1, 3 * natoms[1]], name = "force_"+suffix) if self.use_smooth : @@ -929,6 +1019,19 @@ def build_interaction (self, natoms, n_a_sel = self.nnei_a, n_r_sel = self.nnei_r) + if self.srtab is not None : + sw_virial, sw_atom_virial \ + = op_module.soft_min_virial (energy_diff, + sw_deriv, + rij, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + atom_virial = atom_virial + sw_atom_virial + tab_atom_virial + virial = virial + sw_virial \ + + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, natoms[1], 9]), axis = 1) + virial = tf.reshape (virial, [-1, 9], name = "virial_"+suffix) atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "atom_virial_"+suffix) @@ -936,7 +1039,8 @@ def build_interaction (self, def build_atom_net (self, nframes, - inputs, + inputs, + fparam, natoms, bias_atom_e = None, reuse = None) : @@ -964,12 +1068,22 @@ def build_atom_net (self, layer = self._DS_layer_type_ext(inputs_i, name='DS_layer_type_'+str(type_i), natoms=natoms, reuse=reuse, seed = self.seed) else : layer = self._DS_layer(inputs_i, name='DS_layer_type_'+str(type_i), natoms=natoms, reuse=reuse, seed = self.seed) + if self.numb_fparam > 0 : + ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) + ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) + ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) + layer = tf.concat([layer, ext_fparam], axis = 1) for ii in range(0,len(self.n_neuron)) : if ii >= 1 and self.n_neuron[ii] == self.n_neuron[ii-1] : layer+= self._one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i), reuse=reuse, seed = self.seed, use_timestep = self.resnet_dt) else : layer = self._one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i), reuse=reuse, seed = self.seed) else : + if self.numb_fparam > 0 : + ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) + ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) + ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) + layer = tf.concat([layer, ext_fparam], axis = 1) layer = self._one_layer(inputs_i, self.n_neuron[0], name='layer_0_type_'+str(type_i), reuse=reuse, seed = self.seed) for ii in range(1,len(self.n_neuron)) : layer = self._one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i), reuse=reuse, seed = self.seed) diff --git a/source/train/TabInter.py b/source/train/TabInter.py new file mode 100644 index 0000000000..e6de0bb42e --- /dev/null +++ b/source/train/TabInter.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +import os, sys, shutil +import numpy as np +from scipy.interpolate import CubicSpline + +class TabInter (object): + def __init__(self, + filename): + self.reinit(filename) + + def reinit(self, + filename): + self.vdata = np.loadtxt(filename) + self.rmin = self.vdata[0][0] + self.hh = self.vdata[1][0] - self.vdata[0][0] + self.nspline = self.vdata.shape[0] - 1 + ncol = self.vdata.shape[1] - 1 + n0 = (-1 + np.sqrt(1 + 8 * ncol)) * 0.5 + self.ntypes = int(n0 + 0.1) + assert(self.ntypes * (self.ntypes+1) // 2 == ncol),\ + "number of volumes provided in %s does not match guessed number of types %d" % (filename, self.ntypes) + self.tab_info = np.array([self.rmin, self.hh, self.nspline, self.ntypes]) + self.tab_data = self._make_data() + + def get(self) : + return self.tab_info, self.tab_data + + def _make_data(self) : + data = np.zeros([self.ntypes * self.ntypes * 4 * self.nspline]) + stride = 4 * self.nspline + idx_iter = 0 + xx = self.vdata[:,0] + for t0 in range(self.ntypes) : + for t1 in range(t0, self.ntypes) : + vv = self.vdata[:,1+idx_iter] + cs = CubicSpline(xx, vv) + dd = cs(xx, 1) + dd *= self.hh + dtmp = np.zeros(stride) + for ii in range(self.nspline) : + dtmp[ii*4+0] = 2 * vv[ii] - 2 * vv[ii+1] + dd[ii] + dd[ii+1] + dtmp[ii*4+1] =-3 * vv[ii] + 3 * vv[ii+1] - 2 * dd[ii] - dd[ii+1] + dtmp[ii*4+2] = dd[ii] + dtmp[ii*4+3] = vv[ii] + data[(t0 * self.ntypes + t1) * stride : (t0 * self.ntypes + t1) * stride + stride] \ + = dtmp + data[(t1 * self.ntypes + t0) * stride : (t1 * self.ntypes + t0) * stride + stride] \ + = dtmp + idx_iter += 1 + return data diff --git a/source/train/Test.py b/source/train/Test.py index 406c8d99a8..2af6884d6e 100644 --- a/source/train/Test.py +++ b/source/train/Test.py @@ -6,6 +6,7 @@ import numpy as np import glob import tensorflow as tf +from TabInter import TabInter from tensorflow.python.framework import ops @@ -18,6 +19,8 @@ sys.path.append (module_path ) import _prod_force_grad import _prod_virial_grad +import _soft_min_force_grad +import _soft_min_virial_grad class DataSets (object): def __init__ (self, @@ -48,34 +51,44 @@ def load_test_set (self, box_test = np.load (set_name + "/box.npy") # dirty workaround, type in type.raw should be sorted type_test = np.loadtxt (set_name + "/../type.raw") + natoms = type_test.shape[0] + idx = np.arange (natoms) + self.idx_map = np.lexsort ((idx, type_test)) + atom_type3 = np.array([type_test[ii//3] for ii in range (natoms * 3)]) + idx3 = np.arange (natoms * 3) + self.idx3_map = np.lexsort ((idx3, atom_type3)) self.coord_test0 = np.array([coord_test[0]]) self.box_test0 = np.array([box_test[0]]) self.type_test0 = np.array([type_test]) + self.coord_test0 = self.coord_test0[:, self.idx3_map] + self.type_test0 = self.type_test0[:, self.idx_map] - self.coord_test = [coord_test[0]] - self.box_test = [box_test[0]] - self.type_test = np.array([type_test]) + self.coord_test = self.coord_test0 + self.box_test = self.box_test0 + self.type_test = self.type_test0 coord0 = np.copy (self.coord_test[0]) - self.natoms = self.type_test[0].shape[0] for ii in range(self.natoms * 3) : p_coord = np.copy (coord0) n_coord = np.copy (coord0) p_coord[ii] += self.hh n_coord[ii] -= self.hh - self.coord_test.append (p_coord) - self.coord_test.append (n_coord) - self.box_test.append (box_test[0]) - self.box_test.append (box_test[0]) + self.coord_test = np.append(self.coord_test, p_coord) + self.coord_test = np.append(self.coord_test, n_coord) + self.box_test = np.append(self.box_test, box_test[0]) + self.box_test = np.append(self.box_test, box_test[0]) + self.coord_test = np.reshape(self.coord_test, [self.natoms*6+1, -1]) + self.box_test = np.reshape(self.box_test, [self.natoms*6+1, 9]) self.coord_test = np.array(self.coord_test) self.box_test = np.array(self.box_test) self.type_test = np.tile (self.type_test, (2 * self.natoms * 3 + 1, 1)) + # self.type_test = np.tile (self.type_test, (3, 1)) end_time = time.time() - + def get_test (self) : return self.coord_test, self.box_test, self.type_test @@ -131,6 +144,7 @@ def __init__ (self, comp = 0) : self.sess = sess self.natoms = data.get_natoms() + self.ntypes = len(self.natoms) - 2 self.comp = comp self.sel_a = [12,24] self.sel_r = [12,24] @@ -143,14 +157,29 @@ def __init__ (self, self.ndescrpt_a = self.nnei_a * 4 self.ndescrpt_r = self.nnei_r * 1 self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r - davg = np.zeros (self.ndescrpt) - dstd = np.ones (self.ndescrpt) + davg = np.zeros ([self.ntypes, self.ndescrpt]) + dstd = np.ones ([self.ntypes, self.ndescrpt]) self.t_avg = tf.constant(davg.astype(np.float64)) self.t_std = tf.constant(dstd.astype(np.float64)) self.default_mesh = np.zeros (6, dtype = np.int32) self.default_mesh[3] = 2 self.default_mesh[4] = 2 self.default_mesh[5] = 2 + self.srtab = TabInter('tab.xvg') + self.smin_alpha = 0.3 + self.sw_rmin = 1 + self.sw_rmax = 3.45 + tab_info, tab_data = self.srtab.get() + self.tab_info = tf.get_variable('t_tab_info', + tab_info.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) + self.tab_data = tf.get_variable('t_tab_data', + tab_data.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) def net (self, inputs, @@ -210,14 +239,109 @@ def comp_ef (self, n_r_sel = self.nnei_r) return energy, force, virial + def comp_interpl_ef (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + descrpt, descrpt_deriv, rij, nlist, axis \ + = op_module.descrpt (dcoord, + dtype, + tnatoms, + dbox, + tf.constant(self.default_mesh), + self.t_avg, + self.t_std, + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + sel_a = self.sel_a, + sel_r = self.sel_r, + axis_rule = self.axis_rule) + inputs_reshape = tf.reshape (descrpt, [-1, self.ndescrpt]) + atom_ener = self.net (inputs_reshape, name, reuse = reuse) + + sw_lambda, sw_deriv \ + = op_module.soft_min_switch(dtype, + rij, + nlist, + tnatoms, + sel_a = self.sel_a, + sel_r = self.sel_r, + alpha = self.smin_alpha, + rmin = self.sw_rmin, + rmax = self.sw_rmax) + inv_sw_lambda = 1.0 - sw_lambda + tab_atom_ener, tab_force, tab_atom_virial \ + = op_module.tab_inter(self.tab_info, + self.tab_data, + dtype, + rij, + nlist, + tnatoms, + sw_lambda, + sel_a = self.sel_a, + sel_r = self.sel_r) + energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, self.natoms[0]]) + tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) + atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener + energy_raw = tab_atom_ener + atom_ener + + energy_raw = tf.reshape(energy_raw, [-1, self.natoms[0]]) + energy = tf.reduce_sum (energy_raw, axis = 1) + + net_deriv_ = tf.gradients (atom_ener, inputs_reshape) + net_deriv = net_deriv_[0] + net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) + + force = op_module.prod_force (net_deriv_reshape, + descrpt_deriv, + nlist, + axis, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + sw_force \ + = op_module.soft_min_force(energy_diff, + sw_deriv, + nlist, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + force = force + sw_force + tab_force + virial, atom_vir = op_module.prod_virial (net_deriv_reshape, + descrpt_deriv, + rij, + nlist, + axis, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + sw_virial, sw_atom_virial \ + = op_module.soft_min_virial (energy_diff, + sw_deriv, + rij, + nlist, + tnatoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + # atom_virial = atom_virial + sw_atom_virial + tab_atom_virial + virial = virial + sw_virial \ + + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, self.natoms[1], 9]), axis = 1) + + return energy, force, virial + + # mimic loss term of force def comp_fl (self, dcoord, dbox, dtype, tnatoms, name, + c_ef, reuse = None) : - energy, force, virial = self.comp_ef (dcoord, dbox, dtype, tnatoms, name, reuse) + energy, force, virial = c_ef (dcoord, dbox, dtype, tnatoms, name, reuse) with tf.variable_scope(name, reuse=True): net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) f_mag = tf.reduce_sum (tf.nn.tanh(force)) @@ -225,14 +349,17 @@ def comp_fl (self, assert (len(f_mag_dw) == 1), "length of dw is wrong" return f_mag, f_mag_dw[0] + + # mimic loss term of virial def comp_vl (self, dcoord, dbox, dtype, tnatoms, name, + c_ef, reuse = None) : - energy, force, virial = self.comp_ef (dcoord, dbox, dtype, tnatoms, name, reuse) + energy, force, virial = c_ef (dcoord, dbox, dtype, tnatoms, name, reuse) with tf.variable_scope(name, reuse=True): net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) v_mag = tf.reduce_sum (virial) @@ -265,13 +392,14 @@ def make_feed_dict0 (self, } def test_force (self, - data) : + data, + c_ef) : self.make_place () feed_dict_test = self.make_feed_dict (data) feed_dict_test0 = self.make_feed_dict0 (data) self.net_w_i = 1 * np.ones (self.ndescrpt) - t_energy, t_force, t_virial = self.comp_ef (self.coord, self.box, self.type, self.tnatoms, name = "test_0") + t_energy, t_force, t_virial = c_ef (self.coord, self.box, self.type, self.tnatoms, name = "test_0") self.sess.run (tf.global_variables_initializer()) energy = self.sess.run (t_energy, feed_dict = feed_dict_test) force = self.sess.run (t_force , feed_dict = feed_dict_test) @@ -295,12 +423,14 @@ def test_force (self, print ("max absolute %e" % np.max(absolut_e)) print ("max relative %e" % np.max(relativ_e)) + def comp_vol (self, box) : return np.linalg.det (np.reshape(box, (3,3))) def test_virial (self, - data ) : + data, + c_ef) : hh = 1e-6 self.make_place () @@ -313,7 +443,7 @@ def test_virial (self, self.net_w_i = 1 * np.ones (self.ndescrpt) - t_energy, t_force, t_virial = self.comp_ef (self.coord, self.box, self.type, self.tnatoms, name = "test_0") + t_energy, t_force, t_virial = c_ef (self.coord, self.box, self.type, self.tnatoms, name = "test_0") self.sess.run (tf.global_variables_initializer()) virial = self.sess.run (t_virial , feed_dict = feed_dict_box) energy = self.sess.run (t_energy , feed_dict = feed_dict_box) @@ -332,15 +462,17 @@ def test_virial (self, for dd in range (3) : print ( "dir %d: ana %14.5e num %14.5e diff %.2e" % (dd, ana_v[dd], num_v[dd], np.abs(ana_v[dd] - num_v[dd])) ) + def test_dw (self, - data) : + data, + c_ef) : self.make_place () feed_dict_test0 = self.make_feed_dict0 (data) w0 = np.ones (self.ndescrpt) self.net_w_i = np.copy(w0) - t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_0") + t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_0", c_ef = c_ef) self.sess.run (tf.global_variables_initializer()) ll_0 = self.sess.run (t_ll, feed_dict = feed_dict_test0) dw_0 = self.sess.run (t_dw, feed_dict = feed_dict_test0) @@ -351,11 +483,11 @@ def test_dw (self, for ii in range (self.ndescrpt) : self.net_w_i = np.copy (w0) self.net_w_i[ii] += hh - t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+1)) + t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+1), c_ef = c_ef) self.sess.run (tf.global_variables_initializer()) ll_1 = self.sess.run (t_ll, feed_dict = feed_dict_test0) self.net_w_i[ii] -= 2. * hh - t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+2)) + t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+2), c_ef = c_ef) self.sess.run (tf.global_variables_initializer()) ll_2 = self.sess.run (t_ll, feed_dict = feed_dict_test0) num_v = (ll_1 - ll_2) / (2. * hh) @@ -373,14 +505,15 @@ def test_dw (self, print ("max relative %e" % np.max(relativ_e)) def test_virial_dw (self, - data) : + data, + c_ef) : self.make_place () feed_dict_test0 = self.make_feed_dict0 (data) w0 = np.ones (self.ndescrpt) self.net_w_i = np.copy(w0) - t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_0") + t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_0", c_ef = c_ef) self.sess.run (tf.global_variables_initializer()) ll_0 = self.sess.run (t_ll, feed_dict = feed_dict_test0) dw_0 = self.sess.run (t_dw, feed_dict = feed_dict_test0) @@ -391,11 +524,11 @@ def test_virial_dw (self, for ii in range (self.ndescrpt) : self.net_w_i = np.copy (w0) self.net_w_i[ii] += hh - t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+1)) + t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+1), c_ef = c_ef) self.sess.run (tf.global_variables_initializer()) ll_1 = self.sess.run (t_ll, feed_dict = feed_dict_test0) self.net_w_i[ii] -= 2. * hh - t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+2)) + t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+2), c_ef = c_ef) self.sess.run (tf.global_variables_initializer()) ll_2 = self.sess.run (t_ll, feed_dict = feed_dict_test0) num_v = (ll_1 - ll_2) / (2. * hh) @@ -412,16 +545,21 @@ def test_virial_dw (self, print ("max absolute %e" % np.max(absolut_e)) print ("max relative %e" % np.max(relativ_e)) + def _main () : data = DataSets (set_prefix = "set") tf.reset_default_graph() with tf.Session() as sess: md = Model (sess, data) - md.test_force (data) - # md.test_virial (data) - # md.test_dw (data) - # md.test_virial_dw (data) + # ######################################## + # use md.comp_ef or md.comp_interpl_ef + # ######################################## + # md.test_force (data, md.comp_interpl_ef) + # md.test_virial (data, md.comp_interpl_ef) + # md.test_dw (data, md.comp_interpl_ef) + md.test_virial_dw (data, md.comp_interpl_ef) + if __name__ == '__main__': _main() diff --git a/source/train/__init__.py b/source/train/__init__.py new file mode 100644 index 0000000000..4fe1d7b3f7 --- /dev/null +++ b/source/train/__init__.py @@ -0,0 +1 @@ +from .DeepPot import DeepPot diff --git a/source/train/__main__.py b/source/train/__main__.py new file mode 100644 index 0000000000..ac318edd2e --- /dev/null +++ b/source/train/__main__.py @@ -0,0 +1,74 @@ +import argparse + +from .train import train +from .freeze import freeze +from .config import config +from .test import test + +def _main () : + parser = argparse.ArgumentParser( + description="deepmd-kit") + subparsers = parser.add_subparsers(title='Valid subcommands', dest='command') + + parser_cfig = subparsers.add_parser('config', help='fast configuration of parameter file for smooth model') + parser_cfig.add_argument("-o", "--output", type=str, default = "input.json", + help="the output json file") + + default_num_inter_threads = 0 + parser_train = subparsers.add_parser('train', help='train a model') + parser_train.add_argument('INPUT', + help='the input json database ') + parser_train.add_argument('-t','--inter-threads', type = int, default = default_num_inter_threads, + help= + 'With default value %d. ' % default_num_inter_threads + + 'Setting the "inter_op_parallelism_threads" key for the tensorflow, ' + + 'the "intra_op_parallelism_threads" will be set by the env variable OMP_NUM_THREADS') + parser_train.add_argument('--init-model', type = str, + help= + 'Initialize the model by the provided checkpoint.') + parser_train.add_argument('--restart', type = str, + help= + 'Restart the training from the provided checkpoint.') + + default_frozen_nodes = "energy_test,force_test,virial_test,atom_energy_test,atom_virial_test,t_rcut,t_ntypes,t_dfparam,t_tmap" + parser_frz = subparsers.add_parser('freeze', help='freeze the model') + parser_frz.add_argument("-d", "--folder", type=str, default = ".", + help="path to checkpoint folder") + parser_frz.add_argument("-o", "--output", type=str, default = "frozen_model.pb", + help="name of graph, will output to the checkpoint folder") + parser_frz.add_argument("-n", "--nodes", type=str, default = default_frozen_nodes, + help="the frozen nodes, defaults is " + default_frozen_nodes) + + parser_tst = subparsers.add_parser('test', help='test the model') + parser_tst.add_argument("-m", "--model", default="frozen_model.pb", type=str, + help="Frozen model file to import") + parser_tst.add_argument("-s", "--system", default=".", type=str, + help="The system dir") + parser_tst.add_argument("-S", "--set-prefix", default="set", type=str, + help="The set prefix") + parser_tst.add_argument("-n", "--numb-test", default=100, type=int, + help="The number of data for test") + parser_tst.add_argument("-r", "--rand-seed", type=int, + help="The random seed") + parser_tst.add_argument("--shuffle-test", action = 'store_true', + help="Shuffle test data") + parser_tst.add_argument("-d", "--detail-file", type=str, + help="The file containing details of energy force and virial accuracy") + + args = parser.parse_args() + + if args.command is None : + parser.print_help() + exit + if args.command == 'train' : + train(args) + elif args.command == 'freeze' : + freeze(args) + elif args.command == 'config' : + config(args) + elif args.command == 'test' : + test(args) + else : + raise RuntimeError('unknown command ' + args.command) + +_main() diff --git a/source/train/test.py b/source/train/test.py index 34fe00a5a1..f0f5225778 100755 --- a/source/train/test.py +++ b/source/train/test.py @@ -7,123 +7,32 @@ import numpy as np import tensorflow as tf -lib_path = os.path.dirname(os.path.realpath(__file__)) + "/../lib/" -sys.path.append (lib_path) - -from deepmd.Data import DataSets - +from Data import DataSets +from DeepPot import DeepPot from tensorflow.python.framework import ops -# load force module -module_path = os.path.dirname(os.path.realpath(__file__)) + "/../lib/" -assert (os.path.isfile (module_path + "deepmd/libop_abi.so" )), "force module does not exist" -op_module = tf.load_op_library(module_path + "deepmd/libop_abi.so") - -# load grad of force module -sys.path.append (module_path ) -import deepmd._prod_force_grad -import deepmd._prod_virial_grad - -def load_graph(frozen_graph_filename, - prefix = 'load'): - # We load the protobuf file from the disk and parse it to retrieve the - # unserialized graph_def - with tf.gfile.GFile(frozen_graph_filename, "rb") as f: - graph_def = tf.GraphDef() - graph_def.ParseFromString(f.read()) - - # Then, we can use again a convenient built-in function to import a graph_def into the - # current default Graph - with tf.Graph().as_default() as graph: - tf.import_graph_def( - graph_def, - input_map=None, - return_elements=None, - name=prefix, - producer_op_list=None - ) - return graph - -def rep_int (s): - try: - int(s) - return True - except ValueError: - return False - -def analyze_ntype (graph) : - names = [] - for op in graph.get_operations(): - f1 = op.name.split('/')[1] - if ('layer' in f1) and (not 'gradients'in f1) and (not 'final' in f1) : - f1_fs = f1.split ('_') - assert len(f1_fs) == 4 and rep_int (f1_fs[-1]), "unexpected field of " + f1_fs - names.append (int(f1_fs[-1])) - s_name = sorted(set(names)) - assert len(s_name)-1 == s_name[-1], "the type is not an seq, unexpected" - return len(s_name) - def l2err (diff) : return np.sqrt(np.average (diff*diff)) -def test (sess, data, numb_test = None, detail_file = None) : - graph = sess.graph - ntypes = analyze_ntype (graph) - - natoms_vec = data.get_natoms_vec (ntypes) - natoms_vec = natoms_vec.astype(np.int32) - - test_prop_c, test_energy, test_force, test_virial, test_ae, test_coord, test_box, test_type = data.get_test () - if numb_test > test_coord.shape[0] : - print ("# numb_test %d larger than size of dataset %d, is set to %d" - % (numb_test, test_coord.shape[0], test_coord.shape[0]) ) - numb_test = test_coord.shape[0] - - ncell = np.ones (3, dtype=np.int32) - avg_box = np.average (test_box, axis = 0) - cell_size = 3 - avg_box = np.reshape (avg_box, [3,3]) - for ii in range (3) : - ncell[ii] = int ( np.linalg.norm(avg_box[ii]) / cell_size ) - if (ncell[ii] < 2) : ncell[ii] = 2 - default_mesh = np.zeros (6, dtype = np.int32) - default_mesh[3] = ncell[0] - default_mesh[4] = ncell[1] - default_mesh[5] = ncell[2] - - t_coord = graph.get_tensor_by_name ('load/t_coord:0') - t_type = graph.get_tensor_by_name ('load/t_type:0') - t_natoms = graph.get_tensor_by_name ('load/t_natoms:0') - t_box = graph.get_tensor_by_name ('load/t_box:0') - t_mesh = graph.get_tensor_by_name ('load/t_mesh:0') - - t_energy = graph.get_tensor_by_name ('load/energy_test:0') - t_force = graph.get_tensor_by_name ('load/force_test:0') - t_virial = graph.get_tensor_by_name ('load/virial_test:0') - - energy = [] - force = [] - virial = [] - for ii in range(numb_test) : - feed_dict_test = {t_coord: np.reshape(test_coord [ii:ii+1, :], [-1]), - t_box: test_box [ii:ii+1, :], - t_type: np.reshape(test_type [ii:ii+1, :], [-1]), - t_natoms: natoms_vec, - t_mesh: default_mesh} - tmp_energy, tmp_force, tmp_virial = sess.run ([t_energy, t_force, t_virial], feed_dict = feed_dict_test) - energy.append(tmp_energy) - force .append(tmp_force) - virial.append(tmp_virial) +def test (args) : + if args.rand_seed is not None : + np.random.seed(args.rand_seed % (2**32)) - energy = np.reshape (energy, [numb_test]) - force = np.reshape (force , [numb_test, -1]) - virial = np.reshape (virial, [numb_test, -1]) + data = DataSets (args.system, args.set_prefix, shuffle_test = args.shuffle_test) + test_prop_c, test_energy, test_force, test_virial, test_ae, test_coord, test_box, test_type, test_fparam = data.get_test () + numb_test = args.numb_test + natoms = len(test_type[0]) + dp = DeepPot(args.model) + coord = test_coord[:numb_test].reshape([numb_test, -1]) + box = test_box[:numb_test] + atype = test_type[0] + energy, force, virial, ae, av = dp.eval(coord, box, atype, fparam = test_fparam, atomic = True) l2e = (l2err (energy - test_energy[:numb_test])) l2f = (l2err (force - test_force [:numb_test])) l2v = (l2err (virial - test_virial[:numb_test])) - l2ea= l2e/natoms_vec[0] - l2va= l2v/natoms_vec[0] + l2ea= l2e/natoms + l2va= l2v/natoms # print ("# energies: %s" % energy) print ("# number of test data : %d " % numb_test) @@ -133,6 +42,7 @@ def test (sess, data, numb_test = None, detail_file = None) : print ("Virial L2err : %e eV" % l2v) print ("Virial L2err/Natoms : %e eV" % l2va) + detail_file = args.detail_file if detail_file is not None : pe = np.concatenate((np.reshape(test_energy[:numb_test], [-1,1]), np.reshape(energy, [-1,1])), @@ -150,33 +60,3 @@ def test (sess, data, numb_test = None, detail_file = None) : np.savetxt(detail_file+".v.out", pv, header = 'data_vxx data_vxy data_vxz data_vyx data_vyy data_vyz data_vzx data_vzy data_vzz pred_vxx pred_vxy pred_vxz pred_vyx pred_vyy pred_vyz pred_vzx pred_vzy pred_vzz') -def _main () : - parser = argparse.ArgumentParser() - parser.add_argument("-m", "--model", default="frozen_model.pb", type=str, - help="Frozen model file to import") - parser.add_argument("-s", "--system", default=".", type=str, - help="The system dir") - parser.add_argument("-S", "--set-prefix", default="set", type=str, - help="The set prefix") - parser.add_argument("-n", "--numb-test", default=100, type=int, - help="The number of data for test") - parser.add_argument("-r", "--rand-seed", type=int, - help="The random seed") - parser.add_argument("-d", "--detail-file", type=str, - help="The file containing details of energy force and virial accuracy") - args = parser.parse_args() - - if args.rand_seed is not None : - np.random.seed(args.rand_seed % (2**32)) - - graph = load_graph(args.model) - data = DataSets (args.system, args.set_prefix) - - with tf.Session(graph = graph) as sess: - test (sess, data, args.numb_test, args.detail_file) - - # for op in graph.get_operations(): - # print (op.name) - -if __name__ == '__main__': - _main() diff --git a/source/train/train.py b/source/train/train.py index cdd22ce1da..314e132552 100755 --- a/source/train/train.py +++ b/source/train/train.py @@ -50,25 +50,7 @@ def j_must_have (jdata, key) : else : return jdata[key] -def _main () : - default_num_inter_threads = 0 - parser = argparse.ArgumentParser( - description="*** Train a model. ***") - parser.add_argument('INPUT', - help='the input json database ') - parser.add_argument('-t','--inter-threads', type = int, default = default_num_inter_threads, - help= - 'With default value %d. ' % default_num_inter_threads + - 'Setting the "inter_op_parallelism_threads" key for the tensorflow, ' + - 'the "intra_op_parallelism_threads" will be set by the env variable OMP_NUM_THREADS') - parser.add_argument('--init-model', type = str, - help= - 'Initialize the model by the provided checkpoint.') - parser.add_argument('--restart', type = str, - help= - 'Restart the training from the provided checkpoint.') - args = parser.parse_args() - +def train (args) : # load json database fp = open (args.INPUT, 'r') jdata = json.load (fp) @@ -124,6 +106,3 @@ def _do_work(jdata, run_opt): end_time = time.time() run_opt.message("finished training\nwall time: %.3f s" % (end_time-start_time)) -if __name__ == '__main__': - _main() - From b6abce1a450bee6e9075e0cf6240a1325f472878 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Wed, 19 Jun 2019 07:29:01 +0800 Subject: [PATCH 02/33] grad in one shared lib --- source/op/CMakeLists.txt | 42 +--------------------------- source/op/_prod_force_grad.py | 23 +++++++-------- source/op/_prod_force_norot_grad.py | 21 +++++++------- source/op/_prod_virial_grad.py | 25 +++++++++-------- source/op/_prod_virial_norot_grad.py | 23 +++++++-------- 5 files changed, 49 insertions(+), 85 deletions(-) diff --git a/source/op/CMakeLists.txt b/source/op/CMakeLists.txt index 7e600523a7..066f378b9d 100644 --- a/source/op/CMakeLists.txt +++ b/source/op/CMakeLists.txt @@ -4,7 +4,7 @@ set(OP_LIB ${PROJECT_SOURCE_DIR}/lib/src/SimulationRegion.cpp ${PROJECT_SOURCE_D set (OP_CXX_FLAG -D_GLIBCXX_USE_CXX11_ABI=${OP_ABI} ) file(GLOB OP_SRC prod_force.cc prod_virial.cc descrpt.cc descrpt_norot.cc tab_inter.cc prod_force_norot.cc prod_virial_norot.cc soft_min.cc soft_min_force.cc soft_min_virial.cc ) -file(GLOB OP_GRADS_SRC soft_min_force_grad.cc soft_min_virial_grad.cc) +file(GLOB OP_GRADS_SRC prod_force_grad.cc prod_force_norot_grad.cc prod_virial_grad.cc prod_virial_norot_grad.cc soft_min_force_grad.cc soft_min_virial_grad.cc ) file(GLOB OP_PY *.py) if (BUILD_CPP_IF) @@ -13,53 +13,17 @@ endif (BUILD_CPP_IF) if (BUILD_PY_IF) add_library(op_abi SHARED ${OP_SRC} ${OP_LIB}) add_library(op_grads SHARED ${OP_GRADS_SRC}) - add_library(prod_force_grad SHARED prod_force_grad.cc) - add_library(prod_force_norot_grad SHARED prod_force_norot_grad.cc) - add_library(prod_virial_grad SHARED prod_virial_grad.cc) - add_library(prod_virial_norot_grad SHARED prod_virial_norot_grad.cc) target_link_libraries( op_abi ${TensorFlowFramework_LIBRARY} ) target_link_libraries( op_grads ${TensorFlowFramework_LIBRARY} ) - target_link_libraries( - prod_force_grad ${TensorFlowFramework_LIBRARY} - ) - target_link_libraries( - prod_force_norot_grad ${TensorFlowFramework_LIBRARY} - ) - target_link_libraries( - prod_virial_grad ${TensorFlowFramework_LIBRARY} - ) - target_link_libraries( - prod_virial_norot_grad ${TensorFlowFramework_LIBRARY} - ) set_target_properties( op_abi PROPERTIES COMPILE_FLAGS ${OP_CXX_FLAG} ) - set_target_properties( - prod_force_grad - PROPERTIES - COMPILE_FLAGS ${OP_CXX_FLAG} - ) - set_target_properties( - prod_force_norot_grad - PROPERTIES - COMPILE_FLAGS ${OP_CXX_FLAG} - ) - set_target_properties( - prod_virial_grad - PROPERTIES - COMPILE_FLAGS ${OP_CXX_FLAG} - ) - set_target_properties( - prod_virial_norot_grad - PROPERTIES - COMPILE_FLAGS ${OP_CXX_FLAG} - ) endif (BUILD_PY_IF) if (BUILD_CPP_IF) @@ -68,9 +32,5 @@ endif (BUILD_CPP_IF) if (BUILD_PY_IF) install(TARGETS op_abi DESTINATION deepmd) install(TARGETS op_grads DESTINATION deepmd) - install(TARGETS prod_force_grad DESTINATION deepmd) - install(TARGETS prod_force_norot_grad DESTINATION deepmd) - install(TARGETS prod_virial_grad DESTINATION deepmd) - install(TARGETS prod_virial_norot_grad DESTINATION deepmd) install(FILES ${OP_PY} DESTINATION deepmd) endif (BUILD_PY_IF) diff --git a/source/op/_prod_force_grad.py b/source/op/_prod_force_grad.py index f68076ab79..d07fc2db1e 100644 --- a/source/op/_prod_force_grad.py +++ b/source/op/_prod_force_grad.py @@ -9,18 +9,19 @@ from tensorflow.python.ops import array_ops from tensorflow.python.ops import sparse_ops -force_module_path = os.path.dirname(os.path.realpath(__file__)) + "/" -assert (os.path.isfile (force_module_path + "libprod_force_grad.so" )), "force module grad does not exist" -prod_force_grad_module = tf.load_op_library(force_module_path + 'libprod_force_grad.so') +module_path = os.path.dirname(os.path.realpath(__file__)) +module_file = os.path.join(module_path, 'libop_grads.so') +assert (os.path.isfile(module_file)), 'module op_grads does not exist' +op_grads_module = tf.load_op_library(module_file) @ops.RegisterGradient("ProdForce") def _prod_force_grad_cc (op, grad): - net_grad = prod_force_grad_module.prod_force_grad (grad, - op.inputs[0], - op.inputs[1], - op.inputs[2], - op.inputs[3], - op.inputs[4], - n_a_sel = op.get_attr("n_a_sel"), - n_r_sel = op.get_attr("n_r_sel")) + net_grad = op_grads_module.prod_force_grad (grad, + op.inputs[0], + op.inputs[1], + op.inputs[2], + op.inputs[3], + op.inputs[4], + n_a_sel = op.get_attr("n_a_sel"), + n_r_sel = op.get_attr("n_r_sel")) return [net_grad, None, None, None, None] diff --git a/source/op/_prod_force_norot_grad.py b/source/op/_prod_force_norot_grad.py index 8cf42f92a8..0370b89b99 100644 --- a/source/op/_prod_force_norot_grad.py +++ b/source/op/_prod_force_norot_grad.py @@ -9,17 +9,18 @@ from tensorflow.python.ops import array_ops from tensorflow.python.ops import sparse_ops -force_module_path = os.path.dirname(os.path.realpath(__file__)) + "/" -assert (os.path.isfile (force_module_path + "libprod_force_norot_grad.so" )), "force module grad does not exist" -prod_force_grad_module = tf.load_op_library(force_module_path + 'libprod_force_norot_grad.so') +module_path = os.path.dirname(os.path.realpath(__file__)) +module_file = os.path.join(module_path, 'libop_grads.so') +assert (os.path.isfile(module_file)), 'module op_grads does not exist' +op_grads_module = tf.load_op_library(module_file) @ops.RegisterGradient("ProdForceNorot") def _prod_force_norot_grad_cc (op, grad): - net_grad = prod_force_grad_module.prod_force_norot_grad (grad, - op.inputs[0], - op.inputs[1], - op.inputs[2], - op.inputs[3], - n_a_sel = op.get_attr("n_a_sel"), - n_r_sel = op.get_attr("n_r_sel")) + net_grad = op_grads_module.prod_force_norot_grad (grad, + op.inputs[0], + op.inputs[1], + op.inputs[2], + op.inputs[3], + n_a_sel = op.get_attr("n_a_sel"), + n_r_sel = op.get_attr("n_r_sel")) return [net_grad, None, None, None] diff --git a/source/op/_prod_virial_grad.py b/source/op/_prod_virial_grad.py index 895f3f2d7d..ab1a92cd24 100644 --- a/source/op/_prod_virial_grad.py +++ b/source/op/_prod_virial_grad.py @@ -9,19 +9,20 @@ from tensorflow.python.ops import array_ops from tensorflow.python.ops import sparse_ops -virial_module_path = os.path.dirname(os.path.realpath(__file__)) + "/" -assert (os.path.isfile (virial_module_path + "libprod_virial_grad.so" )), "virial module grad does not exist" -prod_virial_grad_module = tf.load_op_library(virial_module_path + 'libprod_virial_grad.so') +module_path = os.path.dirname(os.path.realpath(__file__)) +module_file = os.path.join(module_path, 'libop_grads.so') +assert (os.path.isfile(module_file)), 'module op_grads does not exist' +op_grads_module = tf.load_op_library(module_file) @ops.RegisterGradient("ProdVirial") def _prod_virial_grad_cc (op, grad, grad_atom): - net_grad = prod_virial_grad_module.prod_virial_grad (grad, - op.inputs[0], - op.inputs[1], - op.inputs[2], - op.inputs[3], - op.inputs[4], - op.inputs[5], - n_a_sel = op.get_attr("n_a_sel"), - n_r_sel = op.get_attr("n_r_sel")) + net_grad = op_grads_module.prod_virial_grad (grad, + op.inputs[0], + op.inputs[1], + op.inputs[2], + op.inputs[3], + op.inputs[4], + op.inputs[5], + n_a_sel = op.get_attr("n_a_sel"), + n_r_sel = op.get_attr("n_r_sel")) return [net_grad, None, None, None, None, None] diff --git a/source/op/_prod_virial_norot_grad.py b/source/op/_prod_virial_norot_grad.py index 894bd85452..441757434b 100644 --- a/source/op/_prod_virial_norot_grad.py +++ b/source/op/_prod_virial_norot_grad.py @@ -9,18 +9,19 @@ from tensorflow.python.ops import array_ops from tensorflow.python.ops import sparse_ops -virial_module_path = os.path.dirname(os.path.realpath(__file__)) + "/" -assert (os.path.isfile (virial_module_path + "libprod_virial_norot_grad.so" )), "virial module grad does not exist" -prod_virial_grad_module = tf.load_op_library(virial_module_path + 'libprod_virial_norot_grad.so') +module_path = os.path.dirname(os.path.realpath(__file__)) +module_file = os.path.join(module_path, 'libop_grads.so') +assert (os.path.isfile(module_file)), 'module op_grads does not exist' +op_grads_module = tf.load_op_library(module_file) @ops.RegisterGradient("ProdVirialNorot") def _prod_virial_norot_grad_cc (op, grad, grad_atom): - net_grad = prod_virial_grad_module.prod_virial_norot_grad (grad, - op.inputs[0], - op.inputs[1], - op.inputs[2], - op.inputs[3], - op.inputs[4], - n_a_sel = op.get_attr("n_a_sel"), - n_r_sel = op.get_attr("n_r_sel")) + net_grad = op_grads_module.prod_virial_norot_grad (grad, + op.inputs[0], + op.inputs[1], + op.inputs[2], + op.inputs[3], + op.inputs[4], + n_a_sel = op.get_attr("n_a_sel"), + n_r_sel = op.get_attr("n_r_sel")) return [net_grad, None, None, None, None] From 1d063576b8526b126feec804f5b2c7d5a40a3d9d Mon Sep 17 00:00:00 2001 From: Han Wang Date: Wed, 19 Jun 2019 07:30:22 +0800 Subject: [PATCH 03/33] human readable input/output shape --- source/train/DeepPot.py | 46 ++++++++++++++++++++++------------------- source/train/test.py | 6 ++++++ 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/source/train/DeepPot.py b/source/train/DeepPot.py index c9f93d5823..38de666dc9 100644 --- a/source/train/DeepPot.py +++ b/source/train/DeepPot.py @@ -109,25 +109,32 @@ def eval(self, coords = np.array(coords) cells = np.array(cells) atom_types = np.array(atom_types, dtype = int) - if coords.ndim == 1: - coords = np.array([coords]) - if cells.ndim == 1: - cells = np.array([cells]) + if self.has_fparam : + assert(fparam is not None) + fparam = np.array(fparam) + + # reshape the inputs + cells = np.reshape(cells, [-1, 9]) + nframes = cells.shape[0] + coords = np.reshape(coords, [nframes, -1]) + natoms = coords.shape[1] // 3 + if self.has_fparam : + fdim = self.get_dim_fparam() + if fparam.size == nframes * fdim : + fparam = np.reshape(fparam, [nframes, fdim]) + elif fparam.size == fdim : + fparam = np.tile(fparam.reshape([-1]), [nframes, 1]) + else : + raise RuntimeError('got wrong size of frame param, should be either %d x %d or %d' % (nframes, fdim, fdim)) # sort inputs coords, atom_types, imap = self._sort_input(coords, atom_types) - # check nframe - nframes = cells.shape[0] - assert(nframes == coords.shape[0]) - # make natoms_vec and default_mesh natoms_vec = self._make_natoms_vec(atom_types) + assert(natoms_vec[0] == natoms) default_mesh = _make_default_mesh(cells) - if self.has_fparam : - assert(fparam is not None) - # evaluate energy = [] force = [] @@ -146,13 +153,13 @@ def eval(self, self.t_av] for ii in range(nframes) : feed_dict_test[self.t_coord] = np.reshape(coords[ii:ii+1, :], [-1]) - feed_dict_test[self.t_box ] = cells[ii:ii+1, :] + feed_dict_test[self.t_box ] = cells[ii:ii+1, :] if self.has_fparam: feed_dict_test[self.t_fparam] = np.reshape(fparam[ii:ii+1, :], [-1]) v_out = self.sess.run (t_out, feed_dict = feed_dict_test) energy.append(v_out[0]) force .append(v_out[1]) - virial.append(v_out[2]) + virial.append(v_out[2]) if atomic: ae.append(v_out[3]) av.append(v_out[4]) @@ -163,15 +170,12 @@ def eval(self, ae = self._reverse_map(np.reshape(ae, [nframes,-1,1]), imap) av = self._reverse_map(np.reshape(av, [nframes,-1,9]), imap) - # standarize the shape of outputs - energy = np.reshape(energy, [nframes]) - force = np.reshape(force, [nframes, -1]) - virial = np.reshape(virial, [nframes, -1]) - if atomic : - ae = np.reshape(ae, [nframes, -1]) - av = np.reshape(av, [nframes, -1]) - + energy = np.reshape(energy, [nframes, 1]) + force = np.reshape(force, [nframes, natoms, 3]) + virial = np.reshape(virial, [nframes, 9]) if atomic: + ae = np.reshape(ae, [nframes, natoms, 1]) + av = np.reshape(av, [nframes, natoms, 9]) return energy, force, virial, ae, av else : return energy, force, virial diff --git a/source/train/test.py b/source/train/test.py index f0f5225778..b8d5c85191 100755 --- a/source/train/test.py +++ b/source/train/test.py @@ -22,11 +22,17 @@ def test (args) : test_prop_c, test_energy, test_force, test_virial, test_ae, test_coord, test_box, test_type, test_fparam = data.get_test () numb_test = args.numb_test natoms = len(test_type[0]) + nframes = test_box.shape[0] dp = DeepPot(args.model) coord = test_coord[:numb_test].reshape([numb_test, -1]) box = test_box[:numb_test] atype = test_type[0] energy, force, virial, ae, av = dp.eval(coord, box, atype, fparam = test_fparam, atomic = True) + energy = energy.reshape([nframes,1]) + force = force.reshape([nframes,-1]) + virial = virial.reshape([nframes,9]) + ae = ae.reshape([nframes,-1]) + av = av.reshape([nframes,-1]) l2e = (l2err (energy - test_energy[:numb_test])) l2f = (l2err (force - test_force [:numb_test])) From d1f5e36e23f843644966f1eb65eb1e92f07cfe11 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Wed, 19 Jun 2019 07:31:49 +0800 Subject: [PATCH 04/33] infer nframes, simpified models. only compatible to higher versions of tf --- source/train/Model.py | 123 +++++++++++------------------------------- 1 file changed, 31 insertions(+), 92 deletions(-) diff --git a/source/train/Model.py b/source/train/Model.py index fdef14b20f..48b10b9e62 100644 --- a/source/train/Model.py +++ b/source/train/Model.py @@ -319,13 +319,8 @@ def _build_network(self, davg, dstd, bias_atom_e): else : self.t_fparam = None - self.batch_size_value = list(set(self.batch_size)) - self.batch_size_value.sort() - self.numb_batch_size_value = len(self.batch_size_value) - - self.energy_frz, self.force_frz, self.virial_frz, self.atom_ener_frz \ - = self.build_interaction (1, - self.t_coord, + self.energy, self.force, self.virial, self.atom_ener \ + = self.build_interaction (self.t_coord, self.t_type, self.t_natoms, self.t_box, @@ -334,70 +329,19 @@ def _build_network(self, davg, dstd, bias_atom_e): bias_atom_e = bias_atom_e, suffix = "test", reuse = False) - self.energy_tst, self.force_tst, self.virial_tst, self.atom_ener_tst \ - = self.build_interaction (self.numb_test, - self.t_coord, - self.t_type, - self.t_natoms, - self.t_box, - self.t_mesh, - self.t_fparam, - bias_atom_e = bias_atom_e, - suffix = "train_test", - reuse = True) - self.energy_bch = [] - self.force_bch = [] - self.virial_bch = [] - self.atom_ener_bch = [] - for ii in range(self.numb_batch_size_value) : - tmp_energy_bch, tmp_force_bch, tmp_virial_bch, tmp_atom_ener_bch \ - = self.build_interaction (self.batch_size_value[ii], - self.t_coord, - self.t_type, - self.t_natoms, - self.t_box, - self.t_mesh, - self.t_fparam, - bias_atom_e = bias_atom_e, - suffix = "train_batch_" + str(self.batch_size_value[ii]), - reuse = True) - self.energy_bch.append(tmp_energy_bch) - self.force_bch.append(tmp_force_bch) - self.virial_bch.append(tmp_virial_bch) - self.atom_ener_bch.append(tmp_atom_ener_bch) - - self.l2_l_tst, self.l2_el_tst, self.l2_fl_tst, self.l2_vl_tst, self.l2_ael_tst \ + + self.l2_l, self.l2_el, self.l2_fl, self.l2_vl, self.l2_ael \ = self.loss (self.t_natoms, \ self.t_prop_c, \ - self.t_energy, self.energy_tst, \ - self.t_force, self.force_tst, \ - self.t_virial, self.virial_tst, \ - self.t_atom_ener, self.atom_ener_tst, \ - suffix = "train_test") - self.l2_l_bch = [] - self.l2_el_bch = [] - self.l2_fl_bch = [] - self.l2_vl_bch = [] - self.l2_ael_bch = [] - for ii in range(self.numb_batch_size_value) : - tmp_l2_l_bch, tmp_l2_el_bch, tmp_l2_fl_bch, tmp_l2_vl_bch, tmp_l2_ael_bch \ - = self.loss (self.t_natoms, \ - self.t_prop_c, \ - self.t_energy, self.energy_bch[ii], \ - self.t_force, self.force_bch[ii], \ - self.t_virial, self.virial_bch[ii], \ - self.t_atom_ener, self.atom_ener_bch[ii], \ - suffix = "train_batch_" + str(self.batch_size_value[ii])) - self.l2_l_bch.append(tmp_l2_l_bch) - self.l2_el_bch.append(tmp_l2_el_bch) - self.l2_fl_bch.append(tmp_l2_fl_bch) - self.l2_vl_bch.append(tmp_l2_vl_bch) - self.l2_ael_bch.append(tmp_l2_ael_bch) + self.t_energy, self.energy, \ + self.t_force, self.force, \ + self.t_virial, self.virial, \ + self.t_atom_ener, self.atom_ener, \ + suffix = "test") self._message("built network") def _build_training(self): - self.train_op = [] trainable_variables = tf.trainable_variables() optimizer = tf.train.AdamOptimizer(learning_rate = self.learning_rate) if self.run_opt.is_distrib : @@ -407,13 +351,12 @@ def _build_training(self): total_num_replicas = self.run_opt.cluster_spec.num_tasks("worker"), name = "sync_replicas") self.sync_replicas_hook = optimizer.make_session_run_hook(self.run_opt.is_chief) - for ii in range(self.numb_batch_size_value) : - grads = tf.gradients(self.l2_l_bch[ii], trainable_variables) - apply_op = optimizer.apply_gradients (zip (grads, trainable_variables), - global_step=self.global_step, - name='train_step') - train_ops = [apply_op] + self._extra_train_ops - self.train_op.append(tf.group(*train_ops)) + grads = tf.gradients(self.l2_l, trainable_variables) + apply_op = optimizer.apply_gradients (zip (grads, trainable_variables), + global_step=self.global_step, + name='train_step') + train_ops = [apply_op] + self._extra_train_ops + self.train_op = tf.group(*train_ops) self._message("built training") def _init_sess_serial(self) : @@ -526,7 +469,6 @@ def train (self, default_mesh \ = data.get_batch (sys_weights = self.sys_weights) cur_batch_size = batch_energy.shape[0] - cur_bs_idx = self.batch_size_value.index(cur_batch_size) feed_dict_batch = {self.t_prop_c: batch_prop_c, self.t_energy: batch_energy, self.t_force: np.reshape(batch_force, [-1]), @@ -541,9 +483,9 @@ def train (self, if self.numb_fparam > 0 : feed_dict_batch[self.t_fparam] = np.reshape(batch_fparam, [-1]) if self.display_in_training and cur_batch == 0 : - self.test_on_the_fly(fp, data, feed_dict_batch, cur_bs_idx) + self.test_on_the_fly(fp, data, feed_dict_batch) if self.timing_in_training : tic = time.time() - self.sess.run([self.train_op[cur_bs_idx]], feed_dict = feed_dict_batch, options=prf_options, run_metadata=prf_run_metadata) + self.sess.run([self.train_op], feed_dict = feed_dict_batch, options=prf_options, run_metadata=prf_run_metadata) if self.timing_in_training : toc = time.time() if self.timing_in_training : train_time += toc - tic cur_batch = self.sess.run(self.global_step) @@ -551,7 +493,7 @@ def train (self, if self.display_in_training and (cur_batch % self.disp_freq == 0) : tic = time.time() - self.test_on_the_fly(fp, data, feed_dict_batch, cur_bs_idx) + self.test_on_the_fly(fp, data, feed_dict_batch) toc = time.time() test_time = toc - tic if self.timing_in_training : @@ -594,8 +536,7 @@ def print_head (self) : def test_on_the_fly (self, fp, data, - feed_dict_batch, - ii) : + feed_dict_batch) : test_prop_c, \ test_energy, test_force, test_virial, test_atom_ener, \ test_coord, test_box, test_type, test_fparam, \ @@ -616,18 +557,18 @@ def test_on_the_fly (self, if self.numb_fparam > 0 : feed_dict_test[self.t_fparam] = np.reshape(test_fparam [:self.numb_test, :], [-1]) error_test, error_e_test, error_f_test, error_v_test, error_ae_test \ - = self.sess.run([self.l2_l_tst, \ - self.l2_el_tst, \ - self.l2_fl_tst, \ - self.l2_vl_tst, \ - self.l2_ael_tst], + = self.sess.run([self.l2_l, \ + self.l2_el, \ + self.l2_fl, \ + self.l2_vl, \ + self.l2_ael], feed_dict=feed_dict_test) error_train, error_e_train, error_f_train, error_v_train, error_ae_train \ - = self.sess.run([self.l2_l_bch[ii], \ - self.l2_el_bch[ii], \ - self.l2_fl_bch[ii], \ - self.l2_vl_bch[ii], \ - self.l2_ael_bch[ii]], + = self.sess.run([self.l2_l, \ + self.l2_el, \ + self.l2_fl, \ + self.l2_vl, \ + self.l2_ael], feed_dict=feed_dict_batch) cur_batch = self.cur_batch current_lr = self.sess.run(self.learning_rate) @@ -888,7 +829,6 @@ def loss (self, return l2_loss, l2_ener_loss, l2_force_loss, l2_virial_loss, l2_atom_ener_loss def build_interaction (self, - nframes, coord_, atype_, natoms, @@ -932,7 +872,7 @@ def build_interaction (self, descrpt_reshape = tf.reshape(descrpt, [-1, self.ndescrpt]) - atom_ener = self.build_atom_net (nframes, descrpt_reshape, fparam, natoms, bias_atom_e = bias_atom_e, reuse = reuse) + atom_ener = self.build_atom_net (descrpt_reshape, fparam, natoms, bias_atom_e = bias_atom_e, reuse = reuse) if self.srtab is not None : sw_lambda, sw_deriv \ @@ -1038,7 +978,6 @@ def build_interaction (self, return energy, force, virial, energy_raw def build_atom_net (self, - nframes, inputs, fparam, natoms, @@ -1088,7 +1027,7 @@ def build_atom_net (self, for ii in range(1,len(self.n_neuron)) : layer = self._one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i), reuse=reuse, seed = self.seed) final_layer = self._one_layer(layer, 1, activation_fn = None, bavg = type_bias_ae, name='final_layer_type_'+str(type_i), reuse=reuse, seed = self.seed) - final_layer = tf.reshape(final_layer, [nframes, natoms[2+type_i]]) + final_layer = tf.reshape(final_layer, [-1, natoms[2+type_i]]) # final_layer = tf.cond (tf.equal(natoms[2+type_i], 0), lambda: tf.zeros((0, 0), dtype=global_tf_float_precision), lambda : tf.reshape(final_layer, [-1, natoms[2+type_i]])) # concat the results From 4bc9383ffc6383cfa1723c336733555bef031127 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Wed, 19 Jun 2019 07:33:08 +0800 Subject: [PATCH 05/33] git ignore output files in lmp and train examples --- examples/lmp/.gitignore | 3 +++ examples/train/.gitignore | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 examples/lmp/.gitignore create mode 100644 examples/train/.gitignore diff --git a/examples/lmp/.gitignore b/examples/lmp/.gitignore new file mode 100644 index 0000000000..0871681801 --- /dev/null +++ b/examples/lmp/.gitignore @@ -0,0 +1,3 @@ +log.lammps +*.pb +*.dump diff --git a/examples/train/.gitignore b/examples/train/.gitignore new file mode 100644 index 0000000000..a543fab633 --- /dev/null +++ b/examples/train/.gitignore @@ -0,0 +1,4 @@ +*.out +*.pb +model.ckpt* +checkpoint From 68f03eb6d874c68385c691c807edc8c53130d826 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Thu, 20 Jun 2019 13:54:24 +0800 Subject: [PATCH 06/33] implement radial only smooth descriptor --- source/lib/include/ComputeDescriptor.h | 84 ++++++ source/op/CMakeLists.txt | 4 +- source/op/_prod_force_se_r_grad.py | 24 ++ source/op/_prod_virial_se_r_grad.py | 25 ++ source/op/descrpt_se_r.cc | 382 +++++++++++++++++++++++++ source/op/prod_force_se_r.cc | 132 +++++++++ source/op/prod_force_se_r_grad.cc | 134 +++++++++ source/op/prod_virial_se_r.cc | 139 +++++++++ source/op/prod_virial_se_r_grad.cc | 138 +++++++++ source/tests/CMakeLists.txt | 2 +- source/tests/common.py | 12 +- source/tests/test_descrpt_se_r.py | 163 +++++++++++ source/train/CMakeLists.txt | 2 +- 13 files changed, 1235 insertions(+), 6 deletions(-) create mode 100644 source/op/_prod_force_se_r_grad.py create mode 100644 source/op/_prod_virial_se_r_grad.py create mode 100644 source/op/descrpt_se_r.cc create mode 100644 source/op/prod_force_se_r.cc create mode 100644 source/op/prod_force_se_r_grad.cc create mode 100644 source/op/prod_virial_se_r.cc create mode 100644 source/op/prod_virial_se_r_grad.cc create mode 100644 source/tests/test_descrpt_se_r.py diff --git a/source/lib/include/ComputeDescriptor.h b/source/lib/include/ComputeDescriptor.h index b76c62bc48..737702fc39 100644 --- a/source/lib/include/ComputeDescriptor.h +++ b/source/lib/include/ComputeDescriptor.h @@ -82,6 +82,21 @@ void compute_descriptor_norot (vector & descrpt_a, const double & rmin, const double & rmax); +inline +void compute_descriptor_se_r (vector & descrpt_r, + vector & descrpt_r_deriv, + vector & rij_r, + const vector & posi, + const int & ntypes, + const vector & type, + const SimulationRegion & region, + const bool & b_pbc, + const int & i_idx, + const vector & fmt_nlist_r, + const vector & sec_r, + const double & rmin, + const double & rmax); + struct NeighborInfo { @@ -1002,4 +1017,73 @@ void compute_descriptor_norot (vector & descrpt_a, } +void compute_descriptor_se_r (vector & descrpt, + vector & descrpt_deriv, + vector & rij, + const vector & posi, + const int & ntypes, + const vector & type, + const SimulationRegion & region, + const bool & b_pbc, + const int & i_idx, + const vector & fmt_nlist, + const vector & sec, + const double & rmin, + const double & rmax) +{ + // compute the diff of the neighbors + vector > sel_diff (sec.back()); + rij.resize (sec.back() * 3); + fill (rij.begin(), rij.end(), 0.0); + for (int ii = 0; ii < int(sec.size()) - 1; ++ii){ + for (int jj = sec[ii]; jj < sec[ii+1]; ++jj){ + if (fmt_nlist[jj] < 0) break; + sel_diff[jj].resize(3); + const int & j_idx = fmt_nlist[jj]; + if (b_pbc){ + region.diffNearestNeighbor (posi[j_idx*3+0], posi[j_idx*3+1], posi[j_idx*3+2], + posi[i_idx*3+0], posi[i_idx*3+1], posi[i_idx*3+2], + sel_diff[jj][0], sel_diff[jj][1], sel_diff[jj][2]); + } + else { + for (int dd = 0; dd < 3; ++dd) sel_diff[jj][dd] = posi[j_idx*3+dd] - posi[i_idx*3+dd]; + } + for (int dd = 0; dd < 3; ++dd) rij[jj*3+dd] = sel_diff[jj][dd]; + } + } + + // 1./rr + descrpt.resize (sec.back()); + fill (descrpt.begin(), descrpt.end(), 0.0); + // deriv wrt center: 3 + descrpt_deriv.resize (sec.back() * 3); + fill (descrpt_deriv.begin(), descrpt_deriv.end(), 0.0); + + for (int sec_iter = 0; sec_iter < int(sec.size()) - 1; ++sec_iter){ + for (int nei_iter = sec[sec_iter]; nei_iter < sec[sec_iter+1]; ++nei_iter) { + if (fmt_nlist[nei_iter] < 0) break; + const double * rr = &sel_diff[nei_iter][0]; + double nr2 = MathUtilities::dot(rr, rr); + double inr = 1./sqrt(nr2); + double nr = nr2 * inr; + double inr2 = inr * inr; + double inr4 = inr2 * inr2; + double inr3 = inr4 * nr; + double sw, dsw; + spline5_switch(sw, dsw, nr, rmin, rmax); + int idx_deriv = nei_iter * 3; // 1 components time 3 directions + int idx_value = nei_iter; // 1 components + // value components + descrpt[idx_value + 0] = 1./nr; + // deriv of component 1/r + descrpt_deriv[idx_deriv + 0] = rr[0] * inr3 * sw - descrpt[idx_value + 0] * dsw * rr[0] * inr; + descrpt_deriv[idx_deriv + 1] = rr[1] * inr3 * sw - descrpt[idx_value + 0] * dsw * rr[1] * inr; + descrpt_deriv[idx_deriv + 2] = rr[2] * inr3 * sw - descrpt[idx_value + 0] * dsw * rr[2] * inr; + // value components + descrpt[idx_value + 0] *= sw; + } + } +} + + diff --git a/source/op/CMakeLists.txt b/source/op/CMakeLists.txt index 066f378b9d..269c0350fe 100644 --- a/source/op/CMakeLists.txt +++ b/source/op/CMakeLists.txt @@ -3,8 +3,8 @@ set(OP_LIB ${PROJECT_SOURCE_DIR}/lib/src/SimulationRegion.cpp ${PROJECT_SOURCE_DIR}/lib/src/NeighborList.cpp) set (OP_CXX_FLAG -D_GLIBCXX_USE_CXX11_ABI=${OP_ABI} ) -file(GLOB OP_SRC prod_force.cc prod_virial.cc descrpt.cc descrpt_norot.cc tab_inter.cc prod_force_norot.cc prod_virial_norot.cc soft_min.cc soft_min_force.cc soft_min_virial.cc ) -file(GLOB OP_GRADS_SRC prod_force_grad.cc prod_force_norot_grad.cc prod_virial_grad.cc prod_virial_norot_grad.cc soft_min_force_grad.cc soft_min_virial_grad.cc ) +file(GLOB OP_SRC prod_force.cc prod_virial.cc descrpt.cc descrpt_norot.cc descrpt_se_r.cc tab_inter.cc prod_force_norot.cc prod_virial_norot.cc prod_force_se_r.cc prod_virial_se_r.cc soft_min.cc soft_min_force.cc soft_min_virial.cc ) +file(GLOB OP_GRADS_SRC prod_force_grad.cc prod_force_norot_grad.cc prod_force_se_r_grad.cc prod_virial_grad.cc prod_virial_norot_grad.cc prod_virial_se_r_grad.cc soft_min_force_grad.cc soft_min_virial_grad.cc ) file(GLOB OP_PY *.py) if (BUILD_CPP_IF) diff --git a/source/op/_prod_force_se_r_grad.py b/source/op/_prod_force_se_r_grad.py new file mode 100644 index 0000000000..f6b5b7d6df --- /dev/null +++ b/source/op/_prod_force_se_r_grad.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +""" +Gradients for prod force. +""" + +import os +import tensorflow as tf +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import sparse_ops + +module_path = os.path.dirname(os.path.realpath(__file__)) +module_file = os.path.join(module_path, 'libop_grads.so') +assert (os.path.isfile(module_file)), 'module op_grads does not exist' +op_grads_module = tf.load_op_library(module_file) + +@ops.RegisterGradient("ProdForceSeR") +def _prod_force_norot_grad_cc (op, grad): + net_grad = op_grads_module.prod_force_se_r_grad (grad, + op.inputs[0], + op.inputs[1], + op.inputs[2], + op.inputs[3]) + return [net_grad, None, None, None] diff --git a/source/op/_prod_virial_se_r_grad.py b/source/op/_prod_virial_se_r_grad.py new file mode 100644 index 0000000000..7c03ccbd63 --- /dev/null +++ b/source/op/_prod_virial_se_r_grad.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +""" +Gradients for prod virial. +""" + +import os +import tensorflow as tf +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import sparse_ops + +module_path = os.path.dirname(os.path.realpath(__file__)) +module_file = os.path.join(module_path, 'libop_grads.so') +assert (os.path.isfile(module_file)), 'module op_grads does not exist' +op_grads_module = tf.load_op_library(module_file) + +@ops.RegisterGradient("ProdVirialSeR") +def _prod_virial_norot_grad_cc (op, grad, grad_atom): + net_grad = op_grads_module.prod_virial_se_r_grad (grad, + op.inputs[0], + op.inputs[1], + op.inputs[2], + op.inputs[3], + op.inputs[4]) + return [net_grad, None, None, None, None] diff --git a/source/op/descrpt_se_r.cc b/source/op/descrpt_se_r.cc new file mode 100644 index 0000000000..b0bbfed21e --- /dev/null +++ b/source/op/descrpt_se_r.cc @@ -0,0 +1,382 @@ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +#include "ComputeDescriptor.h" +#include "NeighborList.h" + +typedef double boxtensor_t ; +typedef double compute_t; + +using namespace tensorflow; +using namespace std; + +#ifdef HIGH_PREC +typedef double VALUETYPE ; +#else +typedef float VALUETYPE ; +#endif + +REGISTER_OP("DescrptSeR") +#ifdef HIGH_PREC +.Input("coord: double") +.Input("type: int32") +.Input("natoms: int32") +.Input("box: double") +.Input("mesh: int32") +.Input("davg: double") +.Input("dstd: double") +.Attr("rcut: float") +.Attr("rcut_smth: float") +.Attr("sel: list(int)") +.Output("descrpt: double") +.Output("descrpt_deriv: double") +.Output("rij: double") +.Output("nlist: int32"); +#else +.Input("coord: float") +.Input("type: int32") +.Input("natoms: int32") +.Input("box: float") +.Input("mesh: int32") +.Input("davg: float") +.Input("dstd: float") +.Attr("rcut: float") +.Attr("rcut_smth: float") +.Attr("sel: list(int)") +.Output("descrpt: float") +.Output("descrpt_deriv: float") +.Output("rij: float") +.Output("nlist: int32"); +#endif + +class DescrptSeROp : public OpKernel { +public: + explicit DescrptSeROp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("rcut", &rcut)); + OP_REQUIRES_OK(context, context->GetAttr("rcut_smth", &rcut_smth)); + OP_REQUIRES_OK(context, context->GetAttr("sel", &sel)); + cum_sum (sec, sel); + sel_null.resize(3, 0); + cum_sum (sec_null, sel_null); + ndescrpt = sec.back() * 1; + nnei = sec.back(); + fill_nei_a = true; + count_nei_idx_overflow = 0; + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + int context_input_index = 0; + const Tensor& coord_tensor = context->input(context_input_index++); + const Tensor& type_tensor = context->input(context_input_index++); + const Tensor& natoms_tensor = context->input(context_input_index++); + const Tensor& box_tensor = context->input(context_input_index++); + const Tensor& mesh_tensor = context->input(context_input_index++); + const Tensor& avg_tensor = context->input(context_input_index++); + const Tensor& std_tensor = context->input(context_input_index++); + + // set size of the sample + OP_REQUIRES (context, (coord_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of coord should be 2")); + OP_REQUIRES (context, (type_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of type should be 2")); + OP_REQUIRES (context, (natoms_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of natoms should be 1")); + OP_REQUIRES (context, (box_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of box should be 2")); + OP_REQUIRES (context, (mesh_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of mesh should be 1")); + OP_REQUIRES (context, (avg_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of avg should be 2")); + OP_REQUIRES (context, (std_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of std should be 2")); + OP_REQUIRES (context, (fill_nei_a), errors::InvalidArgument ("Rotational free descriptor only support the case rcut_a < 0")); + + OP_REQUIRES (context, (natoms_tensor.shape().dim_size(0) >= 3), errors::InvalidArgument ("number of atoms should be larger than (or equal to) 3")); + auto natoms = natoms_tensor .flat(); + int nloc = natoms(0); + int nall = natoms(1); + int ntypes = natoms_tensor.shape().dim_size(0) - 2; + int nsamples = coord_tensor.shape().dim_size(0); + + // check the sizes + OP_REQUIRES (context, (nsamples == type_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nsamples == box_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (ntypes == avg_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of avg should be ntype")); + OP_REQUIRES (context, (ntypes == std_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of std should be ntype")); + + OP_REQUIRES (context, (nall * 3 == coord_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of atoms should match")); + OP_REQUIRES (context, (nall == type_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of atoms should match")); + OP_REQUIRES (context, (9 == box_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of box should be 9")); + OP_REQUIRES (context, (ndescrpt == avg_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of avg should be ndescrpt")); + OP_REQUIRES (context, (ndescrpt == std_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of std should be ndescrpt")); + + int nei_mode = 0; + if (mesh_tensor.shape().dim_size(0) == 16) { + nei_mode = 3; + } + else if (mesh_tensor.shape().dim_size(0) == 12) { + nei_mode = 2; + } + else if (mesh_tensor.shape().dim_size(0) == 6) { + assert (nloc == nall); + nei_mode = 1; + } + + // Create an output tensor + TensorShape descrpt_shape ; + descrpt_shape.AddDim (nsamples); + descrpt_shape.AddDim (nloc * ndescrpt); + TensorShape descrpt_deriv_shape ; + descrpt_deriv_shape.AddDim (nsamples); + descrpt_deriv_shape.AddDim (nloc * ndescrpt * 3); + TensorShape rij_shape ; + rij_shape.AddDim (nsamples); + rij_shape.AddDim (nloc * nnei * 3); + TensorShape nlist_shape ; + nlist_shape.AddDim (nsamples); + nlist_shape.AddDim (nloc * nnei); + + int context_output_index = 0; + Tensor* descrpt_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(context_output_index++, + descrpt_shape, + &descrpt_tensor)); + Tensor* descrpt_deriv_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(context_output_index++, + descrpt_deriv_shape, + &descrpt_deriv_tensor)); + Tensor* rij_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(context_output_index++, + rij_shape, + &rij_tensor)); + Tensor* nlist_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(context_output_index++, + nlist_shape, + &nlist_tensor)); + + auto coord = coord_tensor .matrix(); + auto type = type_tensor .matrix(); + auto box = box_tensor .matrix(); + auto mesh = mesh_tensor .flat(); + auto avg = avg_tensor .matrix(); + auto std = std_tensor .matrix(); + auto descrpt = descrpt_tensor ->matrix(); + auto descrpt_deriv = descrpt_deriv_tensor ->matrix(); + auto rij = rij_tensor ->matrix(); + auto nlist = nlist_tensor ->matrix(); + + OP_REQUIRES (context, (ntypes == int(sel.size())), errors::InvalidArgument ("number of types should match the length of sel array")); + + for (int kk = 0; kk < nsamples; ++kk){ + // set region + boxtensor_t boxt [9] = {0}; + for (int dd = 0; dd < 9; ++dd) { + boxt[dd] = box(kk, dd); + } + SimulationRegion region; + region.reinitBox (boxt); + + // set & normalize coord + vector d_coord3 (nall*3); + for (int ii = 0; ii < nall; ++ii){ + for (int dd = 0; dd < 3; ++dd){ + d_coord3[ii*3+dd] = coord(kk, ii*3+dd); + } + if (nei_mode <= 1){ + compute_t inter[3]; + region.phys2Inter (inter, &d_coord3[3*ii]); + for (int dd = 0; dd < 3; ++dd){ + if (inter[dd] < 0 ) inter[dd] += 1.; + else if (inter[dd] >= 1) inter[dd] -= 1.; + } + region.inter2Phys (&d_coord3[3*ii], inter); + } + } + + // set type + vector d_type (nall); + for (int ii = 0; ii < nall; ++ii) d_type[ii] = type(kk, ii); + + // build nlist + vector > d_nlist; + vector > d_nlist_null; + vector nlist_map; + bool b_nlist_map = false; + if (nei_mode == 3) { + int * pilist, *pjrange, *pjlist; + memcpy (&pilist, &mesh(4), sizeof(int *)); + memcpy (&pjrange, &mesh(8), sizeof(int *)); + memcpy (&pjlist, &mesh(12), sizeof(int *)); + int inum = mesh(1); + assert (inum == nloc); + d_nlist_null.resize (inum); + d_nlist.resize (inum); + for (unsigned ii = 0; ii < inum; ++ii){ + d_nlist.reserve (pjrange[inum] / inum + 10); + } + for (unsigned ii = 0; ii < inum; ++ii){ + int i_idx = pilist[ii]; + for (unsigned jj = pjrange[ii]; jj < pjrange[ii+1]; ++jj){ + int j_idx = pjlist[jj]; + d_nlist[i_idx].push_back (j_idx); + } + } + } + else if (nei_mode == 2) { + vector nat_stt = {mesh(1-1), mesh(2-1), mesh(3-1)}; + vector nat_end = {mesh(4-1), mesh(5-1), mesh(6-1)}; + vector ext_stt = {mesh(7-1), mesh(8-1), mesh(9-1)}; + vector ext_end = {mesh(10-1), mesh(11-1), mesh(12-1)}; + vector global_grid (3); + for (int dd = 0; dd < 3; ++dd) global_grid[dd] = nat_end[dd] - nat_stt[dd]; + ::build_nlist (d_nlist_null, d_nlist, d_coord3, nloc, -1, rcut, nat_stt, nat_end, ext_stt, ext_end, region, global_grid); + } + else if (nei_mode == 1) { + vector bk_d_coord3 = d_coord3; + vector bk_d_type = d_type; + vector ncell, ngcell; + copy_coord(d_coord3, d_type, nlist_map, ncell, ngcell, bk_d_coord3, bk_d_type, rcut, region); + b_nlist_map = true; + vector nat_stt(3, 0); + vector ext_stt(3), ext_end(3); + for (int dd = 0; dd < 3; ++dd){ + ext_stt[dd] = -ngcell[dd]; + ext_end[dd] = ncell[dd] + ngcell[dd]; + } + ::build_nlist (d_nlist_null, d_nlist, d_coord3, nloc, -1, rcut, nat_stt, ncell, ext_stt, ext_end, region, ncell); + } + else { + build_nlist (d_nlist_null, d_nlist, -1, rcut, d_coord3, region); + } + + bool b_pbc = true; + // if region is given extended, do not use pbc + if (nei_mode >= 1) { + b_pbc = false; + } + + // loop over atoms, compute descriptors for each atom +#pragma omp parallel for + for (int ii = 0; ii < nloc; ++ii){ + vector fmt_nlist_null; + vector fmt_nlist; + int ret = -1; + if (fill_nei_a){ + if ((ret = format_nlist_fill_a (fmt_nlist, fmt_nlist_null, d_coord3, ntypes, d_type, region, b_pbc, ii, d_nlist_null[ii], d_nlist[ii], rcut, sec, sec_null)) != -1){ + if (count_nei_idx_overflow == 0) { + cout << "WARNING: Radial neighbor list length of type " << ret << " is not enough" << endl; + flush(cout); + count_nei_idx_overflow ++; + } + } + } + // cout << ii << " " ; + // for (int jj = 0 ; jj < fmt_nlist.size(); ++jj){ + // cout << fmt_nlist[jj] << " " ; + // } + // cout << endl; + + vector d_descrpt; + vector d_descrpt_deriv; + vector d_rij; + compute_descriptor_se_r (d_descrpt, + d_descrpt_deriv, + d_rij, + d_coord3, + ntypes, + d_type, + region, + b_pbc, + ii, + fmt_nlist, + sec, + rcut_smth, + rcut); + + // check sizes + assert (d_descrpt.size() == ndescrpt_a); + assert (d_descrpt_deriv.size() == ndescrpt * 3); + assert (d_rij.size() == nnei * 3); + assert (int(fmt_nlist.size()) == nnei); + // record outputs + for (int jj = 0; jj < ndescrpt; ++jj) { + descrpt(kk, ii * ndescrpt + jj) = (d_descrpt[jj] - avg(d_type[ii], jj)) / std(d_type[ii], jj); + } + for (int jj = 0; jj < ndescrpt * 3; ++jj) { + descrpt_deriv(kk, ii * ndescrpt * 3 + jj) = d_descrpt_deriv[jj] / std(d_type[ii], jj/3); + } + for (int jj = 0; jj < nnei * 3; ++jj){ + rij (kk, ii * nnei * 3 + jj) = d_rij[jj]; + } + for (int jj = 0; jj < nnei; ++jj){ + int record = fmt_nlist[jj]; + if (b_nlist_map && record >= 0) { + record = nlist_map[record]; + } + nlist (kk, ii * nnei + jj) = record; + } + } + } + } +private: + float rcut; + float rcut_smth; + vector sel; + vector sel_null; + vector sec; + vector sec_null; + int ndescrpt; + int nnei; + bool fill_nei_a; + int count_nei_idx_overflow; + void + cum_sum (vector & sec, + const vector & n_sel) const { + sec.resize (n_sel.size() + 1); + sec[0] = 0; + for (int ii = 1; ii < sec.size(); ++ii){ + sec[ii] = sec[ii-1] + n_sel[ii-1]; + } + } + void + build_nlist (vector > & nlist0, + vector > & nlist1, + const compute_t & rc0_, + const compute_t & rc1_, + const vector & posi3, + const SimulationRegion & region) const { + compute_t rc0 (rc0_); + compute_t rc1 (rc1_); + assert (rc0 <= rc1); + compute_t rc02 = rc0 * rc0; + // negative rc0 means not applying rc0 + if (rc0 < 0) rc02 = 0; + compute_t rc12 = rc1 * rc1; + + unsigned natoms = posi3.size()/3; + nlist0.clear(); + nlist1.clear(); + nlist0.resize(natoms); + nlist1.resize(natoms); + for (unsigned ii = 0; ii < natoms; ++ii){ + nlist0[ii].reserve (60); + nlist1[ii].reserve (60); + } + for (unsigned ii = 0; ii < natoms; ++ii){ + for (unsigned jj = ii+1; jj < natoms; ++jj){ + compute_t diff[3]; + region.diffNearestNeighbor (posi3[jj*3+0], posi3[jj*3+1], posi3[jj*3+2], + posi3[ii*3+0], posi3[ii*3+1], posi3[ii*3+2], + diff[0], diff[1], diff[2]); + compute_t r2 = MathUtilities::dot (diff, diff); + if (r2 < rc02) { + nlist0[ii].push_back (jj); + nlist0[jj].push_back (ii); + } + else if (r2 < rc12) { + nlist1[ii].push_back (jj); + nlist1[jj].push_back (ii); + } + } + } + } +}; + +REGISTER_KERNEL_BUILDER(Name("DescrptSeR").Device(DEVICE_CPU), DescrptSeROp); + diff --git a/source/op/prod_force_se_r.cc b/source/op/prod_force_se_r.cc new file mode 100644 index 0000000000..b4933c5b4a --- /dev/null +++ b/source/op/prod_force_se_r.cc @@ -0,0 +1,132 @@ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +using namespace tensorflow; +using namespace std; + +#ifdef HIGH_PREC +typedef double VALUETYPE; +#else +typedef float VALUETYPE; +#endif + +REGISTER_OP("ProdForceSeR") +#ifdef HIGH_PREC +.Input("net_deriv: double") +.Input("in_deriv: double") +.Input("nlist: int32") +.Input("natoms: int32") +.Output("force: double"); +#else +.Input("net_deriv: float") +.Input("in_deriv: float") +.Input("nlist: int32") +.Input("natoms: int32") +.Output("force: float"); +#endif + +using namespace tensorflow; + +class ProdForceSeROp : public OpKernel { + public: + explicit ProdForceSeROp(OpKernelConstruction* context) : OpKernel(context) { + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + int context_input_index = 0; + const Tensor& net_deriv_tensor = context->input(context_input_index++); + const Tensor& in_deriv_tensor = context->input(context_input_index++); + const Tensor& nlist_tensor = context->input(context_input_index++); + const Tensor& natoms_tensor = context->input(context_input_index++); + + // set size of the sample + OP_REQUIRES (context, (net_deriv_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of net deriv should be 2")); + OP_REQUIRES (context, (in_deriv_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of input deriv should be 2")); + OP_REQUIRES (context, (nlist_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of nlist should be 2")); + OP_REQUIRES (context, (natoms_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of natoms should be 1")); + + OP_REQUIRES (context, (natoms_tensor.shape().dim_size(0) >= 3), errors::InvalidArgument ("number of atoms should be larger than (or equal to) 3")); + auto natoms = natoms_tensor .flat(); + + int nframes = net_deriv_tensor.shape().dim_size(0); + int nloc = natoms(0); + int nall = natoms(1); + int ndescrpt = net_deriv_tensor.shape().dim_size(1) / nloc; + int nnei = nlist_tensor.shape().dim_size(1) / nloc; + + // check the sizes + OP_REQUIRES (context, (nframes == in_deriv_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nframes == nlist_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + + OP_REQUIRES (context, (nloc * ndescrpt * 3 == in_deriv_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of descriptors should match")); + + // Create an output tensor + TensorShape force_shape ; + force_shape.AddDim (nframes); + force_shape.AddDim (3 * nall); + Tensor* force_tensor = NULL; + int context_output_index = 0; + OP_REQUIRES_OK(context, context->allocate_output(context_output_index++, + force_shape, &force_tensor)); + + // flat the tensors + auto net_deriv = net_deriv_tensor.flat(); + auto in_deriv = in_deriv_tensor.flat(); + auto nlist = nlist_tensor.flat(); + auto force = force_tensor->flat(); + + assert (nframes == force_shape.dim_size(0)); + assert (nframes == net_deriv_tensor.shape().dim_size(0)); + assert (nframes == in_deriv_tensor.shape().dim_size(0)); + assert (nframes == nlist_tensor.shape().dim_size(0)); + assert (nall * 3 == force_shape.dim_size(1)); + assert (nloc * ndescrpt == net_deriv_tensor.shape().dim_size(1)); + assert (nloc * ndescrpt * 3 == in_deriv_tensor.shape().dim_size(1)); + assert (nloc * nnei == nlist_tensor.shape().dim_size(1)); + assert (nnei * 1 == ndescrpt); + + // loop over samples +#pragma omp parallel for + for (int kk = 0; kk < nframes; ++kk){ + int force_iter = kk * nall * 3; + int net_iter = kk * nloc * ndescrpt; + int in_iter = kk * nloc * ndescrpt * 3; + int nlist_iter = kk * nloc * nnei; + + for (int ii = 0; ii < nall; ++ii){ + int i_idx = ii; + force (force_iter + i_idx * 3 + 0) = 0; + force (force_iter + i_idx * 3 + 1) = 0; + force (force_iter + i_idx * 3 + 2) = 0; + } + + // compute force of a frame + for (int ii = 0; ii < nloc; ++ii){ + int i_idx = ii; + // deriv wrt center atom + for (int aa = 0; aa < ndescrpt; ++aa){ + force (force_iter + i_idx * 3 + 0) -= net_deriv (net_iter + i_idx * ndescrpt + aa) * in_deriv (in_iter + i_idx * ndescrpt * 3 + aa * 3 + 0); + force (force_iter + i_idx * 3 + 1) -= net_deriv (net_iter + i_idx * ndescrpt + aa) * in_deriv (in_iter + i_idx * ndescrpt * 3 + aa * 3 + 1); + force (force_iter + i_idx * 3 + 2) -= net_deriv (net_iter + i_idx * ndescrpt + aa) * in_deriv (in_iter + i_idx * ndescrpt * 3 + aa * 3 + 2); + } + // deriv wrt neighbors + for (int jj = 0; jj < nnei; ++jj){ + int j_idx = nlist (nlist_iter + i_idx * nnei + jj); + // if (j_idx > nloc) j_idx = j_idx % nloc; + if (j_idx < 0) continue; + force (force_iter + j_idx * 3 + 0) += net_deriv (net_iter + i_idx * ndescrpt + jj) * in_deriv (in_iter + i_idx * ndescrpt * 3 + jj * 3 + 0); + force (force_iter + j_idx * 3 + 1) += net_deriv (net_iter + i_idx * ndescrpt + jj) * in_deriv (in_iter + i_idx * ndescrpt * 3 + jj * 3 + 1); + force (force_iter + j_idx * 3 + 2) += net_deriv (net_iter + i_idx * ndescrpt + jj) * in_deriv (in_iter + i_idx * ndescrpt * 3 + jj * 3 + 2); + } + } + } + } +}; + +REGISTER_KERNEL_BUILDER(Name("ProdForceSeR").Device(DEVICE_CPU), ProdForceSeROp); + + + diff --git a/source/op/prod_force_se_r_grad.cc b/source/op/prod_force_se_r_grad.cc new file mode 100644 index 0000000000..3866ef9b86 --- /dev/null +++ b/source/op/prod_force_se_r_grad.cc @@ -0,0 +1,134 @@ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +using namespace tensorflow; +using namespace std; + +#ifdef HIGH_PREC +typedef double VALUETYPE; +#else +typedef float VALUETYPE; +#endif + +#ifdef HIGH_PREC +REGISTER_OP("ProdForceSeRGrad") +.Input("grad: double") +.Input("net_deriv: double") +.Input("in_deriv: double") +.Input("nlist: int32") +.Input("natoms: int32") +.Output("grad_net: double"); +#else +REGISTER_OP("ProdForceSeRGrad") +.Input("grad: float") +.Input("net_deriv: float") +.Input("in_deriv: float") +.Input("nlist: int32") +.Input("natoms: int32") +.Output("grad_net: float"); +#endif + +class ProdForceSeRGradOp : public OpKernel +{ +public: + explicit ProdForceSeRGradOp(OpKernelConstruction* context) : OpKernel(context) { + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + int context_input_index = 0; + const Tensor& grad_tensor = context->input(context_input_index++); + const Tensor& net_deriv_tensor = context->input(context_input_index++); + const Tensor& in_deriv_tensor = context->input(context_input_index++); + const Tensor& nlist_tensor = context->input(context_input_index++); + const Tensor& natoms_tensor = context->input(context_input_index++); + + // set size of the sample + TensorShape grad_shape = grad_tensor.shape(); + TensorShape net_deriv_shape = net_deriv_tensor.shape(); + TensorShape in_deriv_shape = in_deriv_tensor.shape(); + TensorShape nlist_shape = nlist_tensor.shape(); + + OP_REQUIRES (context, (grad_shape.dims() == 2), errors::InvalidArgument ("Dim of grad should be 2")); + OP_REQUIRES (context, (net_deriv_shape.dims() == 2),errors::InvalidArgument ("Dim of net deriv should be 2")); + OP_REQUIRES (context, (in_deriv_shape.dims() == 2), errors::InvalidArgument ("Dim of input deriv should be 2")); + OP_REQUIRES (context, (nlist_shape.dims() == 2), errors::InvalidArgument ("Dim of nlist should be 2")); + OP_REQUIRES (context, (natoms_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of natoms should be 1")); + + OP_REQUIRES (context, (natoms_tensor.shape().dim_size(0) >= 3), errors::InvalidArgument ("number of atoms should be larger than (or equal to) 3")); + auto natoms = natoms_tensor .flat(); + + int nframes = net_deriv_tensor.shape().dim_size(0); + int nloc = natoms(0); + int ndescrpt = net_deriv_tensor.shape().dim_size(1) / nloc; + int nnei = nlist_tensor.shape().dim_size(1) / nloc; + + // check the sizes + OP_REQUIRES (context, (nframes == grad_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + OP_REQUIRES (context, (nframes == in_deriv_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + OP_REQUIRES (context, (nframes == nlist_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + + OP_REQUIRES (context, (nloc * 3 == grad_shape.dim_size(1)), errors::InvalidArgument ("input grad shape should be 3 x natoms")); + OP_REQUIRES (context, (nloc * ndescrpt * 3 == in_deriv_shape.dim_size(1)),errors::InvalidArgument ("number of descriptors should match")); + + // Create an output tensor + TensorShape grad_net_shape ; + grad_net_shape.AddDim (nframes); + grad_net_shape.AddDim (nloc * ndescrpt); + + // allocate the output tensor + Tensor* grad_net_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(0, grad_net_shape, &grad_net_tensor)); + + // flat the tensors + auto grad = grad_tensor .flat(); + auto net_deriv = net_deriv_tensor .flat(); + auto in_deriv = in_deriv_tensor .flat(); + auto nlist = nlist_tensor .flat(); + auto grad_net = grad_net_tensor ->flat(); + + // loop over frames +#pragma omp parallel for + for (int kk = 0; kk < nframes; ++kk){ + + int grad_iter = kk * nloc * 3; + int net_iter = kk * nloc * ndescrpt; + int in_iter = kk * nloc * ndescrpt * 3; + int nlist_iter = kk * nloc * nnei; + int grad_net_iter = kk * nloc * ndescrpt; + + // reset the frame to 0 + for (int ii = 0; ii < nloc; ++ii){ + for (int aa = 0; aa < ndescrpt; ++aa){ + grad_net (grad_net_iter + ii * ndescrpt + aa) = 0; + } + } + + // compute grad of one frame + for (int ii = 0; ii < nloc; ++ii){ + int i_idx = ii; + + // deriv wrt center atom + for (int aa = 0; aa < ndescrpt; ++aa){ + for (int dd = 0; dd < 3; ++dd){ + grad_net (grad_net_iter + i_idx * ndescrpt + aa) -= grad (grad_iter + i_idx * 3 + dd) * in_deriv (in_iter + i_idx * ndescrpt * 3 + aa * 3 + dd); + } + } + + // loop over neighbors + for (int jj = 0; jj < nnei; ++jj){ + int j_idx = nlist (nlist_iter + i_idx * nnei + jj); + if (j_idx > nloc) j_idx = j_idx % nloc; + if (j_idx < 0) continue; + for (int dd = 0; dd < 3; ++dd){ + grad_net (grad_net_iter + i_idx * ndescrpt + jj) += grad (grad_iter + j_idx * 3 + dd) * in_deriv (in_iter + i_idx * ndescrpt * 3 + jj * 3 + dd); + } + } + } + } + } +}; + +REGISTER_KERNEL_BUILDER(Name("ProdForceSeRGrad").Device(DEVICE_CPU), ProdForceSeRGradOp); diff --git a/source/op/prod_virial_se_r.cc b/source/op/prod_virial_se_r.cc new file mode 100644 index 0000000000..f9b5a71d84 --- /dev/null +++ b/source/op/prod_virial_se_r.cc @@ -0,0 +1,139 @@ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +using namespace tensorflow; +using namespace std; + +#ifdef HIGH_PREC +typedef double VALUETYPE; +#else +typedef float VALUETYPE; +#endif + +#ifdef HIGH_PREC +REGISTER_OP("ProdVirialSeR") +.Input("net_deriv: double") +.Input("in_deriv: double") +.Input("rij: double") +.Input("nlist: int32") +.Input("natoms: int32") +.Output("virial: double") +.Output("atom_virial: double") +; +#else +REGISTER_OP("ProdVirialSeR") +.Input("net_deriv: float") +.Input("in_deriv: float") +.Input("rij: float") +.Input("nlist: int32") +.Input("natoms: int32") +.Output("virial: float") +.Output("atom_virial: float") +; +#endif + +using namespace tensorflow; + +class ProdVirialSeROp : public OpKernel { + public: + explicit ProdVirialSeROp(OpKernelConstruction* context) : OpKernel(context) { + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + int context_input_index = 0; + const Tensor& net_deriv_tensor = context->input(context_input_index++); + const Tensor& in_deriv_tensor = context->input(context_input_index++); + const Tensor& rij_tensor = context->input(context_input_index++); + const Tensor& nlist_tensor = context->input(context_input_index++); + const Tensor& natoms_tensor = context->input(context_input_index++); + + // set size of the sample + OP_REQUIRES (context, (net_deriv_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of net deriv should be 2")); + OP_REQUIRES (context, (in_deriv_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of input deriv should be 2")); + OP_REQUIRES (context, (rij_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of rij should be 2")); + OP_REQUIRES (context, (nlist_tensor.shape().dims() == 2), errors::InvalidArgument ("Dim of nlist should be 2")); + OP_REQUIRES (context, (natoms_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of natoms should be 1")); + + OP_REQUIRES (context, (natoms_tensor.shape().dim_size(0) >= 3), errors::InvalidArgument ("number of atoms should be larger than (or equal to) 3")); + auto natoms = natoms_tensor .flat(); + + int nframes = net_deriv_tensor.shape().dim_size(0); + int nloc = natoms(0); + int nall = natoms(1); + int ndescrpt = net_deriv_tensor.shape().dim_size(1) / nloc; + int nnei = nlist_tensor.shape().dim_size(1) / nloc; + + // check the sizes + OP_REQUIRES (context, (nframes == in_deriv_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nframes == rij_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + OP_REQUIRES (context, (nframes == nlist_tensor.shape().dim_size(0)), errors::InvalidArgument ("number of samples should match")); + + OP_REQUIRES (context, (nloc * ndescrpt * 3 == in_deriv_tensor.shape().dim_size(1)), errors::InvalidArgument ("number of descriptors should match")); + OP_REQUIRES (context, (nloc * nnei * 3 == rij_tensor.shape().dim_size(1)), errors::InvalidArgument ("dim of rij should be nnei * 3")); + + // Create an output tensor + TensorShape virial_shape ; + virial_shape.AddDim (nframes); + virial_shape.AddDim (9); + Tensor* virial_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(0, virial_shape, &virial_tensor)); + TensorShape atom_virial_shape ; + atom_virial_shape.AddDim (nframes); + atom_virial_shape.AddDim (9 * nall); + Tensor* atom_virial_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(1, atom_virial_shape, &atom_virial_tensor)); + + // flat the tensors + auto net_deriv = net_deriv_tensor.flat(); + auto in_deriv = in_deriv_tensor.flat(); + auto rij = rij_tensor.flat(); + auto nlist = nlist_tensor.flat(); + auto virial = virial_tensor->flat(); + auto atom_virial = atom_virial_tensor->flat(); + + // loop over samples +#pragma omp parallel for + for (int kk = 0; kk < nframes; ++kk){ + int net_iter = kk * nloc * ndescrpt; + int in_iter = kk * nloc * ndescrpt * 3; + int rij_iter = kk * nloc * nnei * 3; + int nlist_iter = kk * nloc * nnei; + int virial_iter = kk * 9; + int atom_virial_iter = kk * nall * 9; + + for (int ii = 0; ii < 9; ++ ii){ + virial (virial_iter + ii) = 0.; + } + for (int ii = 0; ii < 9 * nall; ++ ii){ + atom_virial (atom_virial_iter + ii) = 0.; + } + + // compute virial of a frame + for (int ii = 0; ii < nloc; ++ii){ + int i_idx = ii; + + // deriv wrt neighbors + for (int jj = 0; jj < nnei; ++jj){ + int j_idx = nlist (nlist_iter + i_idx * nnei + jj); + if (j_idx < 0) continue; + VALUETYPE pref = -1.0 * net_deriv (net_iter + i_idx * ndescrpt + jj); + for (int dd0 = 0; dd0 < 3; ++dd0){ + for (int dd1 = 0; dd1 < 3; ++dd1){ + VALUETYPE tmp_v = pref * rij (rij_iter + i_idx * nnei * 3 + jj * 3 + dd0) * in_deriv (in_iter + i_idx * ndescrpt * 3 + jj * 3 + dd1); + virial (virial_iter + dd0 * 3 + dd1) -= tmp_v; + atom_virial (atom_virial_iter + j_idx * 9 + dd0 * 3 + dd1) -= tmp_v; + } + } + } + } + } + } +}; + +REGISTER_KERNEL_BUILDER(Name("ProdVirialSeR").Device(DEVICE_CPU), ProdVirialSeROp); + + + diff --git a/source/op/prod_virial_se_r_grad.cc b/source/op/prod_virial_se_r_grad.cc new file mode 100644 index 0000000000..002aa1b907 --- /dev/null +++ b/source/op/prod_virial_se_r_grad.cc @@ -0,0 +1,138 @@ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +using namespace tensorflow; +using namespace std; + +#ifdef HIGH_PREC +typedef double VALUETYPE; +#else +typedef float VALUETYPE; +#endif + +#ifdef HIGH_PREC +REGISTER_OP("ProdVirialSeRGrad") +.Input("grad: double") +.Input("net_deriv: double") +.Input("in_deriv: double") +.Input("rij: double") +.Input("nlist: int32") +.Input("natoms: int32") +.Output("grad_net: double"); +#else +REGISTER_OP("ProdVirialSeRGrad") +.Input("grad: float") +.Input("net_deriv: float") +.Input("in_deriv: float") +.Input("rij: float") +.Input("nlist: int32") +.Input("natoms: int32") +.Output("grad_net: float"); +#endif + +class ProdVirialSeRGradOp : public OpKernel +{ +public: + explicit ProdVirialSeRGradOp(OpKernelConstruction* context) : OpKernel(context) { + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + int context_input_index = 0; + const Tensor& grad_tensor = context->input(context_input_index++); + const Tensor& net_deriv_tensor = context->input(context_input_index++); + const Tensor& in_deriv_tensor = context->input(context_input_index++); + const Tensor& rij_tensor = context->input(context_input_index++); + const Tensor& nlist_tensor = context->input(context_input_index++); + const Tensor& natoms_tensor = context->input(context_input_index++); + + // set size of the sample + TensorShape grad_shape = grad_tensor.shape(); + TensorShape net_deriv_shape = net_deriv_tensor.shape(); + TensorShape in_deriv_shape = in_deriv_tensor.shape(); + TensorShape rij_shape = rij_tensor.shape(); + TensorShape nlist_shape = nlist_tensor.shape(); + + OP_REQUIRES (context, (grad_shape.dims() == 2), errors::InvalidArgument ("Dim of grad should be 2")); + OP_REQUIRES (context, (net_deriv_shape.dims() == 2),errors::InvalidArgument ("Dim of net deriv should be 2")); + OP_REQUIRES (context, (in_deriv_shape.dims() == 2), errors::InvalidArgument ("Dim of input deriv should be 2")); + OP_REQUIRES (context, (rij_shape.dims() == 2), errors::InvalidArgument ("Dim of rij should be 2")); + OP_REQUIRES (context, (nlist_shape.dims() == 2), errors::InvalidArgument ("Dim of nlist should be 2")); + OP_REQUIRES (context, (natoms_tensor.shape().dims() == 1), errors::InvalidArgument ("Dim of natoms should be 1")); + + OP_REQUIRES (context, (natoms_tensor.shape().dim_size(0) >= 3), errors::InvalidArgument ("number of atoms should be larger than (or equal to) 3")); + auto natoms = natoms_tensor .flat(); + + int nframes = net_deriv_tensor.shape().dim_size(0); + int nloc = natoms(0); + int ndescrpt = net_deriv_tensor.shape().dim_size(1) / nloc; + int nnei = nlist_tensor.shape().dim_size(1) / nloc; + + // check the sizes + OP_REQUIRES (context, (nframes == grad_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + OP_REQUIRES (context, (nframes == in_deriv_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + OP_REQUIRES (context, (nframes == rij_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + OP_REQUIRES (context, (nframes == nlist_shape.dim_size(0)), errors::InvalidArgument ("number of frames should match")); + + OP_REQUIRES (context, (9 == grad_shape.dim_size(1)), errors::InvalidArgument ("input grad shape should be 3 x natoms")); + OP_REQUIRES (context, (nloc * ndescrpt * 3 == in_deriv_shape.dim_size(1)),errors::InvalidArgument ("number of descriptors should match")); + OP_REQUIRES (context, (nloc * nnei * 3 == rij_shape.dim_size(1)), errors::InvalidArgument ("dim of rij should be nnei * 3")); + + // Create an output tensor + TensorShape grad_net_shape ; + grad_net_shape.AddDim (nframes); + grad_net_shape.AddDim (nloc * ndescrpt); + + // allocate the output tensor + Tensor* grad_net_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(0, grad_net_shape, &grad_net_tensor)); + + // flat the tensors + auto grad = grad_tensor .flat(); + auto net_deriv = net_deriv_tensor .flat(); + auto in_deriv = in_deriv_tensor .flat(); + auto rij = rij_tensor .flat(); + auto nlist = nlist_tensor .flat(); + auto grad_net = grad_net_tensor ->flat(); + + // loop over frames +#pragma omp parallel for + for (int kk = 0; kk < nframes; ++kk){ + + int grad_iter = kk * 9; + int net_iter = kk * nloc * ndescrpt; + int in_iter = kk * nloc * ndescrpt * 3; + int rij_iter = kk * nloc * nnei * 3; + int nlist_iter = kk * nloc * nnei; + int grad_net_iter = kk * nloc * ndescrpt; + + // reset the frame to 0 + for (int ii = 0; ii < nloc; ++ii){ + for (int aa = 0; aa < ndescrpt; ++aa){ + grad_net (grad_net_iter + ii * ndescrpt + aa) = 0; + } + } + + // compute grad of one frame + for (int ii = 0; ii < nloc; ++ii){ + int i_idx = ii; + + // loop over neighbors + for (int jj = 0; jj < nnei; ++jj){ + int j_idx = nlist (nlist_iter + i_idx * nnei + jj); + if (j_idx < 0) continue; + for (int dd0 = 0; dd0 < 3; ++dd0){ + for (int dd1 = 0; dd1 < 3; ++dd1){ + grad_net (grad_net_iter + i_idx * ndescrpt + jj) -= + -1.0 * grad (grad_iter + dd0 * 3 + dd1) * rij (rij_iter + i_idx * nnei * 3 + jj * 3 + dd0) * in_deriv (in_iter + i_idx * ndescrpt * 3 + jj * 3 + dd1); + } + } + } + } + } + } +}; + +REGISTER_KERNEL_BUILDER(Name("ProdVirialSeRGrad").Device(DEVICE_CPU), ProdVirialSeRGradOp); diff --git a/source/tests/CMakeLists.txt b/source/tests/CMakeLists.txt index cb98e3ba68..38cefeb825 100644 --- a/source/tests/CMakeLists.txt +++ b/source/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -file(GLOB LIB_PY test_descrpt_nonsmth.py test_descrpt_smooth.py test_tab_nonsmth.py test_tab_smooth.py common.py) +file(GLOB LIB_PY *py) install( FILES ${LIB_PY} diff --git a/source/tests/common.py b/source/tests/common.py index 7e130f04ec..38656fd0d8 100644 --- a/source/tests/common.py +++ b/source/tests/common.py @@ -179,7 +179,11 @@ def force_dw_test (inter, relativ_e = [] test_list = range (inter.ndescrpt) ntest = 3 - test_list = np.concatenate((np.arange(0,ntest), np.arange(inter.sel_a[0]*4, inter.sel_a[0]*4+ntest))) + if inter.sel_a[0] != 0: + test_list = np.concatenate((np.arange(0,ntest), np.arange(inter.sel_a[0]*4, inter.sel_a[0]*4+ntest))) + else : + test_list = np.arange(0,ntest) + for ii in test_list: inter.net_w_i = np.copy (w0) inter.net_w_i[ii] += hh @@ -221,7 +225,11 @@ def virial_dw_test (inter, relativ_e = [] test_list = range (inter.ndescrpt) ntest = 3 - test_list = np.concatenate((np.arange(0,ntest), np.arange(inter.sel_a[0]*4, inter.sel_a[0]*4+ntest))) + if inter.sel_a[0] != 0 : + test_list = np.concatenate((np.arange(0,ntest), np.arange(inter.sel_a[0]*4, inter.sel_a[0]*4+ntest))) + else : + test_list = np.arange(0,ntest) + for ii in test_list: inter.net_w_i = np.copy (w0) inter.net_w_i[ii] += hh diff --git a/source/tests/test_descrpt_se_r.py b/source/tests/test_descrpt_se_r.py new file mode 100644 index 0000000000..13c11db5ed --- /dev/null +++ b/source/tests/test_descrpt_se_r.py @@ -0,0 +1,163 @@ +import os,sys +import numpy as np +import tensorflow as tf +import unittest + +from tensorflow.python.framework import ops + +# load force module +module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") +so_file = os.path.join(module_path, "libop_abi.so") +assert (os.path.isfile ( so_file )), "op module does not exist" +op_module = tf.load_op_library( so_file ) + +# load grad of force module +sys.path.append (module_path) +import _prod_force_grad +import _prod_virial_grad +import _prod_force_se_r_grad +import _prod_virial_se_r_grad +import _soft_min_force_grad +import _soft_min_virial_grad + +from common import force_test +from common import virial_test +from common import force_dw_test +from common import virial_dw_test +from common import Data + + +class Inter(): + def __init__ (self, + data) : + self.sess = tf.Session() + self.data = data + self.natoms = self.data.get_natoms() + self.ntypes = self.data.get_ntypes() + self.sel = [12,24] + self.sel_a = [0,0] + self.rcut_smth = 2.45 + self.rcut = 10.0 + self.nnei = np.cumsum(self.sel)[-1] + self.ndescrpt = self.nnei * 1 + davg = np.zeros ([self.ntypes, self.ndescrpt]) + dstd = np.ones ([self.ntypes, self.ndescrpt]) + self.t_avg = tf.constant(davg.astype(np.float64)) + self.t_std = tf.constant(dstd.astype(np.float64)) + self.default_mesh = np.zeros (6, dtype = np.int32) + self.default_mesh[3] = 2 + self.default_mesh[4] = 2 + self.default_mesh[5] = 2 + # make place holder + self.coord = tf.placeholder(tf.float64, [None, self.natoms[0] * 3], name='t_coord') + self.box = tf.placeholder(tf.float64, [None, 9], name='t_box') + self.type = tf.placeholder(tf.int32, [None, self.natoms[0]], name = "t_type") + self.tnatoms = tf.placeholder(tf.int32, [None], name = "t_natoms") + + def _net (self, + inputs, + name, + reuse = False) : + with tf.variable_scope(name, reuse=reuse): + net_w = tf.get_variable ('net_w', + [self.ndescrpt], + tf.float64, + tf.constant_initializer (self.net_w_i)) + dot_v = tf.matmul (tf.reshape (inputs, [-1, self.ndescrpt]), + tf.reshape (net_w, [self.ndescrpt, 1])) + return tf.reshape (dot_v, [-1]) + + def comp_ef (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + descrpt, descrpt_deriv, rij, nlist \ + = op_module.descrpt_se_r (dcoord, + dtype, + tnatoms, + dbox, + tf.constant(self.default_mesh), + self.t_avg, + self.t_std, + rcut = self.rcut, + rcut_smth = self.rcut_smth, + sel = self.sel) + inputs_reshape = tf.reshape (descrpt, [-1, self.ndescrpt]) + atom_ener = self._net (inputs_reshape, name, reuse = reuse) + atom_ener_reshape = tf.reshape(atom_ener, [-1, self.natoms[0]]) + energy = tf.reduce_sum (atom_ener_reshape, axis = 1) + net_deriv_ = tf.gradients (atom_ener, inputs_reshape) + net_deriv = net_deriv_[0] + net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) + + force = op_module.prod_force_se_r (net_deriv_reshape, + descrpt_deriv, + nlist, + tnatoms) + virial, atom_vir = op_module.prod_virial_se_r (net_deriv_reshape, + descrpt_deriv, + rij, + nlist, + tnatoms) + return energy, force, virial + + + def comp_f_dw (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + energy, force, virial = self.comp_ef (dcoord, dbox, dtype, tnatoms, name, reuse) + with tf.variable_scope(name, reuse=True): + net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) + f_mag = tf.reduce_sum (tf.nn.tanh(force)) + f_mag_dw = tf.gradients (f_mag, net_w) + assert (len(f_mag_dw) == 1), "length of dw is wrong" + return f_mag, f_mag_dw[0] + + + def comp_v_dw (self, + dcoord, + dbox, + dtype, + tnatoms, + name, + reuse = None) : + energy, force, virial = self.comp_ef (dcoord, dbox, dtype, tnatoms, name, reuse) + with tf.variable_scope(name, reuse=True): + net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) + v_mag = tf.reduce_sum (virial) + v_mag_dw = tf.gradients (v_mag, net_w) + assert (len(v_mag_dw) == 1), "length of dw is wrong" + return v_mag, v_mag_dw[0] + + + +class TestSmooth(Inter, unittest.TestCase): + def __init__ (self, *args, **kwargs): + self.places = 5 + data = Data() + Inter.__init__(self, data) + unittest.TestCase.__init__(self, *args, **kwargs) + self.controller = object() + + def test_force (self) : + force_test(self, self, places=5, suffix = '_se_r') + + def test_virial (self) : + virial_test(self, self, places=5, suffix = '_se_r') + + def test_force_dw (self) : + force_dw_test(self, self, places=5, suffix = '_se_r') + + def test_virial_dw (self) : + virial_dw_test(self, self, places=5, suffix = '_se_r') + + +if __name__ == '__main__': + unittest.main() diff --git a/source/train/CMakeLists.txt b/source/train/CMakeLists.txt index 0a9d813fd5..6ed171efa2 100644 --- a/source/train/CMakeLists.txt +++ b/source/train/CMakeLists.txt @@ -2,7 +2,7 @@ configure_file("RunOptions.py.in" "${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py" @ONLY) -file(GLOB LIB_PY DeepPot.py Data.py DataSystem.py Model.py Test.py TestNorot.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) +file(GLOB LIB_PY DeepPot.py Data.py DataSystem.py Model.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) file(GLOB CLS_PY Local.py Slurm.py) From 5b1e072748a1474261f3f78bdf4fc15a2902f2b3 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 21 Jun 2019 19:54:54 +0800 Subject: [PATCH 07/33] new variable name convention --- source/lib/src/NNPInter.cc | 148 +++++++++--------------------------- source/tests/common.py | 8 +- source/train/CMakeLists.txt | 2 +- source/train/DeepPot.py | 35 ++++----- source/train/Model.py | 125 +++++++++++++++--------------- source/train/__main__.py | 2 +- 6 files changed, 124 insertions(+), 196 deletions(-) diff --git a/source/lib/src/NNPInter.cc b/source/lib/src/NNPInter.cc index 2d634d1512..0b6c6ea445 100644 --- a/source/lib/src/NNPInter.cc +++ b/source/lib/src/NNPInter.cc @@ -179,21 +179,21 @@ make_input_tensors (std::vector> & input_tensors, if (fparam_.size() == 0) { input_tensors = { - {"t_coord", coord_tensor}, - {"t_type", type_tensor}, - {"t_box", box_tensor}, - {"t_mesh", mesh_tensor}, - {"t_natoms", natoms_tensor}, + {"i_coord", coord_tensor}, + {"i_type", type_tensor}, + {"i_box", box_tensor}, + {"i_mesh", mesh_tensor}, + {"i_natoms", natoms_tensor}, }; } else { input_tensors = { - {"t_coord", coord_tensor}, - {"t_type", type_tensor}, - {"t_box", box_tensor}, - {"t_mesh", mesh_tensor}, - {"t_natoms", natoms_tensor}, - {"t_fparam", fparam_tensor}, + {"i_coord", coord_tensor}, + {"i_type", type_tensor}, + {"i_box", box_tensor}, + {"i_mesh", mesh_tensor}, + {"i_natoms", natoms_tensor}, + {"i_fparam", fparam_tensor}, }; } @@ -299,21 +299,21 @@ make_input_tensors (std::vector> & input_tensors, if (fparam_.size() == 0) { input_tensors = { - {"t_coord", coord_tensor}, - {"t_type", type_tensor}, - {"t_box", box_tensor}, - {"t_mesh", mesh_tensor}, - {"t_natoms", natoms_tensor}, + {"i_coord", coord_tensor}, + {"i_type", type_tensor}, + {"i_box", box_tensor}, + {"i_mesh", mesh_tensor}, + {"i_natoms", natoms_tensor}, }; } else { input_tensors = { - {"t_coord", coord_tensor}, - {"t_type", type_tensor}, - {"t_box", box_tensor}, - {"t_mesh", mesh_tensor}, - {"t_natoms", natoms_tensor}, - {"t_fparam", fparam_tensor}, + {"i_coord", coord_tensor}, + {"i_type", type_tensor}, + {"i_box", box_tensor}, + {"i_mesh", mesh_tensor}, + {"i_natoms", natoms_tensor}, + {"i_fparam", fparam_tensor}, }; } @@ -346,7 +346,7 @@ run_model (ENERGYTYPE & dener, std::vector output_tensors; checkStatus (session->Run(input_tensors, - {"energy_test", "force_test", "virial_test"}, + {"o_energy", "o_force", "o_virial"}, {}, &output_tensors)); @@ -405,7 +405,7 @@ run_model (ENERGYTYPE & dener, std::vector output_tensors; checkStatus (session->Run(input_tensors, - {"energy_test", "force_test", "virial_test", "atom_energy_test", "atom_virial_test"}, + {"o_energy", "o_force", "o_virial", "o_atom_energy", "o_atom_virial"}, {}, &output_tensors)); @@ -486,10 +486,10 @@ NNPInter (const string & model) checkStatus (NewSession(options, &session)); checkStatus (ReadBinaryProto(Env::Default(), model, &graph_def)); checkStatus (session->Create(graph_def)); - rcut = get_scalar("t_rcut"); + rcut = get_scalar("model_attr/t_rcut"); cell_size = rcut; - ntypes = get_scalar("t_ntypes"); - dfparam = get_scalar("t_dfparam"); + ntypes = get_scalar("model_attr/t_ntypes"); + dfparam = get_scalar("model_attr/t_dfparam"); assert(rcut == get_rcut()); assert(ntypes == get_ntypes()); if (dfparam < 0) dfparam = 0; @@ -510,10 +510,10 @@ init (const string & model) checkStatus (NewSession(options, &session)); checkStatus (ReadBinaryProto(Env::Default(), model, &graph_def)); checkStatus (session->Create(graph_def)); - rcut = get_scalar("t_rcut"); + rcut = get_scalar("model_attr/t_rcut"); cell_size = rcut; - ntypes = get_scalar("t_ntypes"); - dfparam = get_scalar("t_dfparam"); + ntypes = get_scalar("model_attr/t_ntypes"); + dfparam = get_scalar("model_attr/t_dfparam"); assert(rcut == get_rcut()); assert(ntypes == get_ntypes()); if (dfparam < 0) dfparam = 0; @@ -555,34 +555,6 @@ get_scalar (const string & name) const return orc(0); } -VALUETYPE -NNPInter:: -get_rcut () const -{ - std::vector output_tensors; - checkStatus (session->Run(std::vector> ({}), - {"t_rcut"}, - {}, - &output_tensors)); - Tensor output_rc = output_tensors[0]; - auto orc = output_rc.flat (); - return orc(0); -} - -int -NNPInter:: -get_ntypes () const -{ - std::vector output_tensors; - checkStatus (session->Run(std::vector> ({}), - {"t_ntypes"}, - {}, - &output_tensors)); - Tensor output_rc = output_tensors[0]; - auto orc = output_rc.flat (); - return orc(0); -} - void NNPInter:: compute (ENERGYTYPE & dener, @@ -725,10 +697,10 @@ NNPInterModelDevi (const vector & models) checkStatus (ReadBinaryProto(Env::Default(), models[ii], &graph_defs[ii])); checkStatus (sessions[ii]->Create(graph_defs[ii])); } - rcut = get_scalar("t_rcut"); + rcut = get_scalar("model_attr/t_rcut"); cell_size = rcut; - ntypes = get_scalar("t_ntypes"); - dfparam = get_scalar("t_dfparam"); + ntypes = get_scalar("model_attr/t_ntypes"); + dfparam = get_scalar("model_attr/t_dfparam"); if (dfparam < 0) dfparam = 0; // rcut = get_rcut(); // cell_size = rcut; @@ -752,10 +724,10 @@ init (const vector & models) checkStatus (ReadBinaryProto(Env::Default(), models[ii], &graph_defs[ii])); checkStatus (sessions[ii]->Create(graph_defs[ii])); } - rcut = get_scalar("t_rcut"); + rcut = get_scalar("model_attr/t_rcut"); cell_size = rcut; - ntypes = get_scalar("t_ntypes"); - dfparam = get_scalar("t_dfparam"); + ntypes = get_scalar("model_attr/t_ntypes"); + dfparam = get_scalar("model_attr/t_dfparam"); if (dfparam < 0) dfparam = 0; // rcut = get_rcut(); // cell_size = rcut; @@ -787,54 +759,6 @@ get_scalar(const string name) const return myrcut; } - -VALUETYPE -NNPInterModelDevi:: -get_rcut () const -{ - VALUETYPE myrcut = 0; - for (unsigned ii = 0; ii < numb_models; ++ii){ - std::vector output_tensors; - checkStatus (sessions[ii]->Run(std::vector> ({}), - {"t_rcut"}, - {}, - &output_tensors)); - Tensor output_rc = output_tensors[0]; - auto orc = output_rc.flat (); - if (ii == 0){ - myrcut = orc(0); - } - else { - assert (myrcut == orc(0)); - } - } - return myrcut; -} - -int -NNPInterModelDevi:: -get_ntypes () const -{ - int myntypes = 0; - for (unsigned ii = 0; ii < numb_models; ++ii){ - std::vector output_tensors; - checkStatus (sessions[ii]->Run(std::vector> ({}), - {"t_ntypes"}, - {}, - &output_tensors)); - Tensor output_rc = output_tensors[0]; - auto orc = output_rc.flat (); - if (ii == 0){ - myntypes = orc(0); - } - else { - assert (myntypes == orc(0)); - } - } - return myntypes; -} - - void NNPInterModelDevi:: compute (ENERGYTYPE & dener, diff --git a/source/tests/common.py b/source/tests/common.py index 38656fd0d8..e715327ef4 100644 --- a/source/tests/common.py +++ b/source/tests/common.py @@ -1,13 +1,17 @@ +import os, sys import tensorflow as tf import numpy as np class Data(): - def __init__ (self) : + def __init__ (self, + rand_pert = 0.1, + seed = 1) : coord = [[0.0, 0.0, 0.1], [1.1, 0.0, 0.1], [0.0, 1.1, 0.1], [4.0, 0.0, 0.0], [5.1, 0.0, 0.0], [4.0, 1.1, 0.0]] self.coord = np.array(coord) - self.coord += 0.1 * np.random.random(self.coord.shape) + np.random.seed(seed) + self.coord += rand_pert * np.random.random(self.coord.shape) self.atype = np.array([0, 1, 1, 0, 1, 1], dtype = int) self.cell = 20 * np.eye(3) self.nframes = 1 diff --git a/source/train/CMakeLists.txt b/source/train/CMakeLists.txt index 6ed171efa2..02eded5f84 100644 --- a/source/train/CMakeLists.txt +++ b/source/train/CMakeLists.txt @@ -2,7 +2,7 @@ configure_file("RunOptions.py.in" "${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py" @ONLY) -file(GLOB LIB_PY DeepPot.py Data.py DataSystem.py Model.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) +file(GLOB LIB_PY common.py DeepPot.py Data.py DataSystem.py Model*.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) file(GLOB CLS_PY Local.py Slurm.py) diff --git a/source/train/DeepPot.py b/source/train/DeepPot.py index 38de666dc9..b4ac06b819 100644 --- a/source/train/DeepPot.py +++ b/source/train/DeepPot.py @@ -59,26 +59,27 @@ def __init__(self, self.model_file = model_file self.graph = _load_graph (self.model_file) # checkout input/output tensors from graph - self.t_ntypes = self.graph.get_tensor_by_name ('load/t_ntypes:0') - self.t_rcut = self.graph.get_tensor_by_name ('load/t_rcut:0') - self.t_dfparam= self.graph.get_tensor_by_name ('load/t_dfparam:0') - self.t_tmap = self.graph.get_tensor_by_name ('load/t_tmap:0') - - self.t_coord = self.graph.get_tensor_by_name ('load/t_coord:0') - self.t_type = self.graph.get_tensor_by_name ('load/t_type:0') - self.t_natoms = self.graph.get_tensor_by_name ('load/t_natoms:0') - self.t_box = self.graph.get_tensor_by_name ('load/t_box:0') - self.t_mesh = self.graph.get_tensor_by_name ('load/t_mesh:0') - self.t_energy = self.graph.get_tensor_by_name ('load/energy_test:0') - self.t_force = self.graph.get_tensor_by_name ('load/force_test:0') - self.t_virial = self.graph.get_tensor_by_name ('load/virial_test:0') - self.t_ae = self.graph.get_tensor_by_name ('load/atom_energy_test:0') - self.t_av = self.graph.get_tensor_by_name ('load/atom_virial_test:0') + self.t_ntypes = self.graph.get_tensor_by_name ('load/model_attr/ntypes:0') + self.t_rcut = self.graph.get_tensor_by_name ('load/model_attr/rcut:0') + self.t_dfparam= self.graph.get_tensor_by_name ('load/model_attr/dfparam:0') + self.t_tmap = self.graph.get_tensor_by_name ('load/model_attr/tmap:0') + # inputs + self.t_coord = self.graph.get_tensor_by_name ('load/i_coord:0') + self.t_type = self.graph.get_tensor_by_name ('load/i_type:0') + self.t_natoms = self.graph.get_tensor_by_name ('load/i_natoms:0') + self.t_box = self.graph.get_tensor_by_name ('load/i_box:0') + self.t_mesh = self.graph.get_tensor_by_name ('load/i_mesh:0') + # outputs + self.t_energy = self.graph.get_tensor_by_name ('load/o_energy:0') + self.t_force = self.graph.get_tensor_by_name ('load/o_force:0') + self.t_virial = self.graph.get_tensor_by_name ('load/o_virial:0') + self.t_ae = self.graph.get_tensor_by_name ('load/o_atom_energy:0') + self.t_av = self.graph.get_tensor_by_name ('load/o_atom_virial:0') self.t_fparam = None # check if the graph has fparam for op in self.graph.get_operations(): - if op.name == 'load/t_fparam' : - self.t_fparam = self.graph.get_tensor_by_name ('load/t_fparam:0') + if op.name == 'load/i_fparam' : + self.t_fparam = self.graph.get_tensor_by_name ('load/i_fparam:0') self.has_fparam = self.t_fparam is not None # start a tf session associated to the graph self.sess = tf.Session (graph = self.graph) diff --git a/source/train/Model.py b/source/train/Model.py index 48b10b9e62..2decf4cc9f 100644 --- a/source/train/Model.py +++ b/source/train/Model.py @@ -11,6 +11,8 @@ from deepmd.RunOptions import global_ener_float_precision from deepmd.RunOptions import global_cvt_2_tf_float from deepmd.RunOptions import global_cvt_2_ener_float +# from ModelSeA import ModelSeA +# from ModelLocFrame import ModelLocFrame from tensorflow.python.framework import ops from tensorflow.python.client import timeline @@ -31,25 +33,7 @@ from deepmd.RunOptions import RunOptions from deepmd.TabInter import TabInter -def j_must_have (jdata, key) : - if not key in jdata.keys() : - raise RuntimeError ("json database must provide key " + key ) - else : - return jdata[key] - -def j_must_have_d (jdata, key, deprecated_key) : - if not key in jdata.keys() : - # raise RuntimeError ("json database must provide key " + key ) - for ii in deprecated_key : - if ii in jdata.keys() : - warnings.warn("the key \"%s\" is deprecated, please use \"%s\" instead" % (ii,key)) - return jdata[ii] - raise RuntimeError ("json database must provide key " + key ) - else : - return jdata[key] - -def j_have (jdata, key) : - return key in jdata.keys() +from deepmd.common import j_must_have, j_must_have_d, j_have def _is_subdir(path, directory): path = os.path.realpath(path) @@ -88,7 +72,6 @@ def __init__(self, run_opt): self.run_opt = run_opt self._init_param(jdata) - self.null_mesh = tf.constant ([-1]) def _init_param(self, jdata): # descrpt config @@ -216,7 +199,7 @@ def build (self, else : raise RuntimeError("number of frame parameter == 0") - t_tmap = tf.constant(' '.join(data.get_type_map()), name = 't_tmap', dtype = tf.string) + self.type_map = data.get_type_map() davg, dstd, bias_e = self._data_stat(data) @@ -275,47 +258,20 @@ def _build_lr(self, lr): self._message("built lr") def _build_network(self, davg, dstd, bias_atom_e): - self.t_avg = tf.get_variable('t_avg', - davg.shape, - dtype = global_tf_float_precision, - trainable = False, - initializer = tf.constant_initializer(davg, dtype = global_tf_float_precision)) - self.t_std = tf.get_variable('t_std', - dstd.shape, - dtype = global_tf_float_precision, - trainable = False, - initializer = tf.constant_initializer(dstd, dtype = global_tf_float_precision)) - - t_rcut = tf.constant(np.max([self.rcut_r, self.rcut_a]), name = 't_rcut', dtype = global_tf_float_precision) - t_ntypes = tf.constant(self.ntypes, name = 't_ntypes', dtype = tf.int32) - t_dfparam = tf.constant(self.numb_fparam, name = 't_dfparam', dtype = tf.int32) - - if self.srtab is not None : - tab_info, tab_data = self.srtab.get() - self.tab_info = tf.get_variable('t_tab_info', - tab_info.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) - self.tab_data = tf.get_variable('t_tab_data', - tab_data.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) self.t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') self.t_energy = tf.placeholder(global_ener_float_precision, [None], name='t_energy') self.t_force = tf.placeholder(global_tf_float_precision, [None], name='t_force') self.t_virial = tf.placeholder(global_tf_float_precision, [None], name='t_virial') self.t_atom_ener = tf.placeholder(global_tf_float_precision, [None], name='t_atom_ener') - self.t_coord = tf.placeholder(global_tf_float_precision, [None], name='t_coord') - self.t_type = tf.placeholder(tf.int32, [None], name='t_type') - self.t_natoms = tf.placeholder(tf.int32, [self.ntypes+2], name='t_natoms') - self.t_box = tf.placeholder(global_tf_float_precision, [None, 9], name='t_box') - self.t_mesh = tf.placeholder(tf.int32, [None], name='t_mesh') + self.t_coord = tf.placeholder(global_tf_float_precision, [None], name='i_coord') + self.t_type = tf.placeholder(tf.int32, [None], name='i_type') + self.t_natoms = tf.placeholder(tf.int32, [self.ntypes+2], name='i_natoms') + self.t_box = tf.placeholder(global_tf_float_precision, [None, 9], name='i_box') + self.t_mesh = tf.placeholder(tf.int32, [None], name='i_mesh') self.is_training = tf.placeholder(tf.bool) if self.numb_fparam > 0 : - self.t_fparam = tf.placeholder(global_tf_float_precision, [None], name='t_fparam') + self.t_fparam = tf.placeholder(global_tf_float_precision, [None], name='i_fparam') else : self.t_fparam = None @@ -326,8 +282,10 @@ def _build_network(self, davg, dstd, bias_atom_e): self.t_box, self.t_mesh, self.t_fparam, + davg = davg, + dstd = dstd, bias_atom_e = bias_atom_e, - suffix = "test", + suffix = "", reuse = False) self.l2_l, self.l2_el, self.l2_fl, self.l2_vl, self.l2_ael \ @@ -700,7 +658,6 @@ def compute_dstats_sys_nonsmth (self, sysv2.append(sumv2) return sysv, sysv2, sysn - def compute_std (self,sumv2, sumv, sumn) : return np.sqrt(sumv2/sumn - np.multiply(sumv/sumn, sumv/sumn)) @@ -835,9 +792,51 @@ def build_interaction (self, box, mesh, fparam, - suffix = 'inter', + davg = None, + dstd = None, bias_atom_e = None, - reuse = None): + suffix = '', + reuse = None): + with tf.variable_scope('model_attr' + suffix, reuse = reuse) : + if davg is None: + davg = np.zeros([self.ntypes, self.ndescrpt]) + if dstd is None: + dstd = np.ones ([self.ntypes, self.ndescrpt]) + t_rcut = tf.constant(np.max([self.rcut_r, self.rcut_a]), + name = 'rcut', + dtype = global_tf_float_precision) + t_ntypes = tf.constant(self.ntypes, + name = 'ntypes', + dtype = tf.int32) + t_dfparam = tf.constant(self.numb_fparam, + name = 'dfparam', + dtype = tf.int32) + t_tmap = tf.constant(' '.join(self.type_map), + name = 'tmap', + dtype = tf.string) + self.t_avg = tf.get_variable('t_avg', + davg.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(davg, dtype = global_tf_float_precision)) + self.t_std = tf.get_variable('t_std', + dstd.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(dstd, dtype = global_tf_float_precision)) + if self.srtab is not None : + tab_info, tab_data = self.srtab.get() + self.tab_info = tf.get_variable('t_tab_info', + tab_info.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) + self.tab_data = tf.get_variable('t_tab_data', + tab_data.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) + coord = tf.reshape (coord_, [-1, natoms[1] * 3]) atype = tf.reshape (atype_, [-1, natoms[1]]) @@ -906,8 +905,8 @@ def build_interaction (self, else : energy_raw = atom_ener - energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'atom_energy_'+suffix) - energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='energy_'+suffix) + energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'o_atom_energy'+suffix) + energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='o_energy'+suffix) net_deriv_tmp = tf.gradients (atom_ener, descrpt_reshape) net_deriv = net_deriv_tmp[0] @@ -938,7 +937,7 @@ def build_interaction (self, n_r_sel = self.nnei_r) force = force + sw_force + tab_force - force = tf.reshape (force, [-1, 3 * natoms[1]], name = "force_"+suffix) + force = tf.reshape (force, [-1, 3 * natoms[1]], name = "o_force"+suffix) if self.use_smooth : virial, atom_virial \ @@ -972,8 +971,8 @@ def build_interaction (self, virial = virial + sw_virial \ + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, natoms[1], 9]), axis = 1) - virial = tf.reshape (virial, [-1, 9], name = "virial_"+suffix) - atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "atom_virial_"+suffix) + virial = tf.reshape (virial, [-1, 9], name = "o_virial"+suffix) + atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "o_atom_virial"+suffix) return energy, force, virial, energy_raw diff --git a/source/train/__main__.py b/source/train/__main__.py index ac318edd2e..94f248be40 100644 --- a/source/train/__main__.py +++ b/source/train/__main__.py @@ -30,7 +30,7 @@ def _main () : help= 'Restart the training from the provided checkpoint.') - default_frozen_nodes = "energy_test,force_test,virial_test,atom_energy_test,atom_virial_test,t_rcut,t_ntypes,t_dfparam,t_tmap" + default_frozen_nodes = "o_energy,o_force,o_virial,o_atom_energy,o_atom_virial,model_attr/rcut,model_attr/ntypes,model_attr/dfparam,model_attr/tmap" parser_frz = subparsers.add_parser('freeze', help='freeze the model') parser_frz.add_argument("-d", "--folder", type=str, default = ".", help="path to checkpoint folder") From b721960c9d5c61ee161f9e929c7d76f77673bc10 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 21 Jun 2019 20:55:36 +0800 Subject: [PATCH 08/33] implement models in seperate classes, add tests for them --- source/tests/CMakeLists.txt | 2 +- source/tests/test_descrpt_nonsmth.py | 10 +- source/tests/test_descrpt_se_r.py | 8 +- source/tests/test_descrpt_smooth.py | 8 +- source/tests/test_model_loc_frame.py | 127 ++++++ source/tests/test_model_se_a.py | 126 ++++++ source/tests/test_tab_nonsmth.py | 8 +- source/tests/test_tab_smooth.py | 8 +- source/tests/water.json | 48 +++ source/tests/water_smth.json | 48 +++ source/train/ModelLocFrame.py | 367 +++++++++++++++++ source/train/ModelSeA.py | 574 +++++++++++++++++++++++++++ source/train/common.py | 20 + 13 files changed, 1323 insertions(+), 31 deletions(-) create mode 100644 source/tests/test_model_loc_frame.py create mode 100644 source/tests/test_model_se_a.py create mode 100644 source/tests/water.json create mode 100644 source/tests/water_smth.json create mode 100644 source/train/ModelLocFrame.py create mode 100644 source/train/ModelSeA.py create mode 100644 source/train/common.py diff --git a/source/tests/CMakeLists.txt b/source/tests/CMakeLists.txt index 38cefeb825..6078cf977a 100644 --- a/source/tests/CMakeLists.txt +++ b/source/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -file(GLOB LIB_PY *py) +file(GLOB LIB_PY *py *json) install( FILES ${LIB_PY} diff --git a/source/tests/test_descrpt_nonsmth.py b/source/tests/test_descrpt_nonsmth.py index 9184791ae7..1cf7c15bb6 100644 --- a/source/tests/test_descrpt_nonsmth.py +++ b/source/tests/test_descrpt_nonsmth.py @@ -5,13 +5,8 @@ from tensorflow.python.framework import ops -# load force module -module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") -so_file = os.path.join(module_path, "libop_abi.so") -assert (os.path.isfile ( so_file )), "op module does not exist" -op_module = tf.load_op_library( so_file ) - # load grad of force module +module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") sys.path.append (module_path) import _prod_force_grad import _prod_virial_grad @@ -24,6 +19,8 @@ from common import virial_dw_test from common import Data +from deepmd.ModelLocFrame import op_module + class Inter(): def __init__ (self, data, @@ -56,6 +53,7 @@ def __init__ (self, self.box = tf.placeholder(tf.float64, [None, 9], name='t_box') self.type = tf.placeholder(tf.int32, [None, self.natoms[0]], name = "t_type") self.tnatoms = tf.placeholder(tf.int32, [None], name = "t_natoms") + def _net (self, inputs, diff --git a/source/tests/test_descrpt_se_r.py b/source/tests/test_descrpt_se_r.py index 13c11db5ed..db61bed7d3 100644 --- a/source/tests/test_descrpt_se_r.py +++ b/source/tests/test_descrpt_se_r.py @@ -5,13 +5,8 @@ from tensorflow.python.framework import ops -# load force module -module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") -so_file = os.path.join(module_path, "libop_abi.so") -assert (os.path.isfile ( so_file )), "op module does not exist" -op_module = tf.load_op_library( so_file ) - # load grad of force module +module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") sys.path.append (module_path) import _prod_force_grad import _prod_virial_grad @@ -26,6 +21,7 @@ from common import virial_dw_test from common import Data +from deepmd.ModelLocFrame import op_module class Inter(): def __init__ (self, diff --git a/source/tests/test_descrpt_smooth.py b/source/tests/test_descrpt_smooth.py index 556baaec8a..c6a46bdcef 100644 --- a/source/tests/test_descrpt_smooth.py +++ b/source/tests/test_descrpt_smooth.py @@ -5,13 +5,8 @@ from tensorflow.python.framework import ops -# load force module -module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") -so_file = os.path.join(module_path, "libop_abi.so") -assert (os.path.isfile ( so_file )), "op module does not exist" -op_module = tf.load_op_library( so_file ) - # load grad of force module +module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") sys.path.append (module_path) import _prod_force_grad import _prod_virial_grad @@ -26,6 +21,7 @@ from common import virial_dw_test from common import Data +from deepmd.ModelLocFrame import op_module class Inter(): def __init__ (self, diff --git a/source/tests/test_model_loc_frame.py b/source/tests/test_model_loc_frame.py new file mode 100644 index 0000000000..56231c5dee --- /dev/null +++ b/source/tests/test_model_loc_frame.py @@ -0,0 +1,127 @@ +import dpdata,os,sys,json,unittest +import numpy as np +import tensorflow as tf +from common import Data + +lib_path = os.path.dirname(os.path.realpath(__file__)) + ".." +sys.path.append (lib_path) + +from deepmd.RunOptions import RunOptions +from deepmd.DataSystem import DataSystem +from deepmd.ModelLocFrame import ModelLocFrame +from deepmd.common import j_must_have, j_must_have_d, j_have + +global_ener_float_precision = tf.float64 +global_tf_float_precision = tf.float64 +global_np_float_precision = np.float64 + +def gen_data() : + tmpdata = Data(rand_pert = 0.1, seed = 1) + sys = dpdata.LabeledSystem() + sys.data['coords'] = tmpdata.coord + sys.data['atom_types'] = tmpdata.atype + sys.data['cells'] = tmpdata.cell + nframes = tmpdata.nframes + natoms = tmpdata.natoms + sys.data['coords'] = sys.data['coords'].reshape([nframes,natoms,3]) + sys.data['cells'] = sys.data['cells'].reshape([nframes,3,3]) + sys.data['energies'] = np.zeros([nframes,1]) + sys.data['forces'] = np.zeros([nframes,natoms,3]) + sys.data['virials'] = [] + sys.to_deepmd_npy('system', prec=np.float64) + +class TestModel(unittest.TestCase): + def setUp(self) : + gen_data() + + def test_model(self): + jfile = 'water.json' + with open(jfile) as fp: + jdata = json.load (fp) + run_opt = RunOptions(None) + systems = j_must_have(jdata, 'systems') + set_pfx = j_must_have(jdata, 'set_prefix') + batch_size = j_must_have(jdata, 'batch_size') + test_size = j_must_have(jdata, 'numb_test') + batch_size = 1 + test_size = 1 + stop_batch = j_must_have(jdata, 'stop_batch') + rcut = j_must_have (jdata, 'rcut') + + data = DataSystem(systems, set_pfx, batch_size, test_size, rcut, run_opt = None) + + test_prop_c, \ + test_energy, test_force, test_virial, test_atom_ener, \ + test_coord, test_box, test_type, test_fparam, \ + natoms_vec, \ + default_mesh \ + = data.get_test () + numb_test = 1 + + bias_atom_e = data.compute_energy_shift() + + model = ModelLocFrame(jdata) + davg, dstd = model.compute_dstats([test_coord], [test_box], [test_type], [natoms_vec], [default_mesh]) + + t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') + t_energy = tf.placeholder(global_ener_float_precision, [None], name='t_energy') + t_force = tf.placeholder(global_tf_float_precision, [None], name='t_force') + t_virial = tf.placeholder(global_tf_float_precision, [None], name='t_virial') + t_atom_ener = tf.placeholder(global_tf_float_precision, [None], name='t_atom_ener') + t_coord = tf.placeholder(global_tf_float_precision, [None], name='i_coord') + t_type = tf.placeholder(tf.int32, [None], name='i_type') + t_natoms = tf.placeholder(tf.int32, [model.ntypes+2], name='i_natoms') + t_box = tf.placeholder(global_tf_float_precision, [None, 9], name='i_box') + t_mesh = tf.placeholder(tf.int32, [None], name='i_mesh') + is_training = tf.placeholder(tf.bool) + t_fparam = None + + energy, force, virial, atom_ener \ + = model.build_interaction (t_coord, + t_type, + t_natoms, + t_box, + t_mesh, + t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "loc_frame", + reuse_attr = False, + reuse_weights = False) + + feed_dict_test = {t_prop_c: test_prop_c, + t_energy: test_energy [:numb_test], + t_force: np.reshape(test_force [:numb_test, :], [-1]), + t_virial: np.reshape(test_virial [:numb_test, :], [-1]), + t_atom_ener: np.reshape(test_atom_ener[:numb_test, :], [-1]), + t_coord: np.reshape(test_coord [:numb_test, :], [-1]), + t_box: test_box [:numb_test, :], + t_type: np.reshape(test_type [:numb_test, :], [-1]), + t_natoms: natoms_vec, + t_mesh: default_mesh, + is_training: False} + + sess = tf.Session() + sess.run(tf.global_variables_initializer()) + [e, f, v] = sess.run([energy, force, virial], + feed_dict = feed_dict_test) + + e = e.reshape([-1]) + f = f.reshape([-1]) + v = v.reshape([-1]) + refe = [1.165945032784766511e+01] + reff = [2.356319331246305437e-01,1.772322096063349284e-01,1.455439548950788684e-02,1.968599426000810226e-01,2.648214484898352983e-01,7.595232354012236564e-02,-2.121321856338151401e-01,-2.463886119018566037e-03,-2.075636300914874069e-02,-9.360310077571798101e-03,-1.751965198776750943e-01,-2.046405309983102827e-02,-1.990194093283037535e-01,-1.828347741191920298e-02,-6.916374506995154325e-02,-1.197997068502068031e-02,-2.461097746875573200e-01,1.987744214930105627e-02] + refv = [-4.998509978510510265e-01,-1.966169437179327711e-02,1.136130543869883977e-02,-1.966169437179334650e-02,-4.575353297894450555e-01,-2.668666556859019493e-03,1.136130543869887100e-02,-2.668666556859039876e-03,2.455466940358383508e-03] + refe = np.reshape(refe, [-1]) + reff = np.reshape(reff, [-1]) + refv = np.reshape(refv, [-1]) + + places = 10 + for ii in range(e.size) : + self.assertAlmostEqual(e[ii], refe[ii], places = places) + for ii in range(f.size) : + self.assertAlmostEqual(f[ii], reff[ii], places = places) + for ii in range(v.size) : + self.assertAlmostEqual(v[ii], refv[ii], places = places) + diff --git a/source/tests/test_model_se_a.py b/source/tests/test_model_se_a.py new file mode 100644 index 0000000000..f247601ebd --- /dev/null +++ b/source/tests/test_model_se_a.py @@ -0,0 +1,126 @@ +import dpdata,os,sys,json,unittest +import numpy as np +import tensorflow as tf +from common import Data + +lib_path = os.path.dirname(os.path.realpath(__file__)) + ".." +sys.path.append (lib_path) + +from deepmd.RunOptions import RunOptions +from deepmd.DataSystem import DataSystem +from deepmd.ModelSeA import ModelSeA +from deepmd.common import j_must_have, j_must_have_d, j_have + +global_ener_float_precision = tf.float64 +global_tf_float_precision = tf.float64 +global_np_float_precision = np.float64 + +def gen_data() : + tmpdata = Data(rand_pert = 0.1, seed = 1) + sys = dpdata.LabeledSystem() + sys.data['coords'] = tmpdata.coord + sys.data['atom_types'] = tmpdata.atype + sys.data['cells'] = tmpdata.cell + nframes = tmpdata.nframes + natoms = tmpdata.natoms + sys.data['coords'] = sys.data['coords'].reshape([nframes,natoms,3]) + sys.data['cells'] = sys.data['cells'].reshape([nframes,3,3]) + sys.data['energies'] = np.zeros([nframes,1]) + sys.data['forces'] = np.zeros([nframes,natoms,3]) + sys.data['virials'] = [] + sys.to_deepmd_npy('system', prec=np.float64) + +class TestModel(unittest.TestCase): + def setUp(self) : + gen_data() + + def test_model(self): + jfile = 'water_smth.json' + with open(jfile) as fp: + jdata = json.load (fp) + run_opt = RunOptions(None) + systems = j_must_have(jdata, 'systems') + set_pfx = j_must_have(jdata, 'set_prefix') + batch_size = j_must_have(jdata, 'batch_size') + test_size = j_must_have(jdata, 'numb_test') + batch_size = 1 + test_size = 1 + stop_batch = j_must_have(jdata, 'stop_batch') + rcut = j_must_have (jdata, 'rcut') + + data = DataSystem(systems, set_pfx, batch_size, test_size, rcut, run_opt = None) + + test_prop_c, \ + test_energy, test_force, test_virial, test_atom_ener, \ + test_coord, test_box, test_type, test_fparam, \ + natoms_vec, \ + default_mesh \ + = data.get_test () + numb_test = 1 + + bias_atom_e = data.compute_energy_shift() + + model = ModelSeA(jdata) + davg, dstd = model.compute_dstats([test_coord], [test_box], [test_type], [natoms_vec], [default_mesh]) + + t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') + t_energy = tf.placeholder(global_ener_float_precision, [None], name='t_energy') + t_force = tf.placeholder(global_tf_float_precision, [None], name='t_force') + t_virial = tf.placeholder(global_tf_float_precision, [None], name='t_virial') + t_atom_ener = tf.placeholder(global_tf_float_precision, [None], name='t_atom_ener') + t_coord = tf.placeholder(global_tf_float_precision, [None], name='i_coord') + t_type = tf.placeholder(tf.int32, [None], name='i_type') + t_natoms = tf.placeholder(tf.int32, [model.ntypes+2], name='i_natoms') + t_box = tf.placeholder(global_tf_float_precision, [None, 9], name='i_box') + t_mesh = tf.placeholder(tf.int32, [None], name='i_mesh') + is_training = tf.placeholder(tf.bool) + t_fparam = None + + energy, force, virial, atom_ener \ + = model.build_interaction (t_coord, + t_type, + t_natoms, + t_box, + t_mesh, + t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "se_a", + reuse_attr = False, + reuse_weights = False) + + feed_dict_test = {t_prop_c: test_prop_c, + t_energy: test_energy [:numb_test], + t_force: np.reshape(test_force [:numb_test, :], [-1]), + t_virial: np.reshape(test_virial [:numb_test, :], [-1]), + t_atom_ener: np.reshape(test_atom_ener[:numb_test, :], [-1]), + t_coord: np.reshape(test_coord [:numb_test, :], [-1]), + t_box: test_box [:numb_test, :], + t_type: np.reshape(test_type [:numb_test, :], [-1]), + t_natoms: natoms_vec, + t_mesh: default_mesh, + is_training: False} + + sess = tf.Session() + sess.run(tf.global_variables_initializer()) + [e, f, v] = sess.run([energy, force, virial], + feed_dict = feed_dict_test) + + e = e.reshape([-1]) + f = f.reshape([-1]) + v = v.reshape([-1]) + refe = [6.135449167779321300e+01] + reff = [7.799691562262310585e-02,9.423098804815030483e-02,3.790560997388224204e-03,1.432522403799846578e-01,1.148392791403983204e-01,-1.321871172563671148e-02,-7.318966526325138000e-02,6.516069212737778116e-02,5.406418483320515412e-04,5.870713761026503247e-02,-1.605402669549013672e-01,-5.089516979826595386e-03,-2.554593467731766654e-01,3.092063507347833987e-02,1.510355029451411479e-02,4.869271842355533952e-02,-1.446113274345035005e-01,-1.126524434771078789e-03] + refv = [-6.076776685178300053e-01,1.103174323630009418e-01,1.984250991380156690e-02,1.103174323630009557e-01,-3.319759402259439551e-01,-6.007404107650986258e-03,1.984250991380157036e-02,-6.007404107650981921e-03,-1.200076017439753642e-03] + refe = np.reshape(refe, [-1]) + reff = np.reshape(reff, [-1]) + refv = np.reshape(refv, [-1]) + + places = 10 + for ii in range(e.size) : + self.assertAlmostEqual(e[ii], refe[ii], places = places) + for ii in range(f.size) : + self.assertAlmostEqual(f[ii], reff[ii], places = places) + for ii in range(v.size) : + self.assertAlmostEqual(v[ii], refv[ii], places = places) diff --git a/source/tests/test_tab_nonsmth.py b/source/tests/test_tab_nonsmth.py index 12de7aaebb..d1e3f153d0 100644 --- a/source/tests/test_tab_nonsmth.py +++ b/source/tests/test_tab_nonsmth.py @@ -5,13 +5,8 @@ from tensorflow.python.framework import ops -# load force module -module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") -so_file = os.path.join(module_path, "libop_abi.so") -assert (os.path.isfile ( so_file )), "op module does not exist" -op_module = tf.load_op_library( so_file ) - # load grad of force module +module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") sys.path.append (module_path) import _prod_force_grad import _prod_virial_grad @@ -28,6 +23,7 @@ from common import Data from test_descrpt_nonsmth import Inter +from deepmd.ModelLocFrame import op_module def _make_tab(ntype) : xx = np.arange(0,9,0.001) diff --git a/source/tests/test_tab_smooth.py b/source/tests/test_tab_smooth.py index 856a25c7ad..029aebf602 100644 --- a/source/tests/test_tab_smooth.py +++ b/source/tests/test_tab_smooth.py @@ -5,13 +5,8 @@ from tensorflow.python.framework import ops -# load force module -module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") -so_file = os.path.join(module_path, "libop_abi.so") -assert (os.path.isfile ( so_file )), "op module does not exist" -op_module = tf.load_op_library( so_file ) - # load grad of force module +module_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") sys.path.append (module_path) import _prod_force_grad import _prod_virial_grad @@ -28,6 +23,7 @@ from common import Data from test_descrpt_smooth import Inter +from deepmd.ModelLocFrame import op_module def _make_tab(ntype) : xx = np.arange(0,9,0.001) diff --git a/source/tests/water.json b/source/tests/water.json new file mode 100644 index 0000000000..1499f974a2 --- /dev/null +++ b/source/tests/water.json @@ -0,0 +1,48 @@ +{ + "with_distrib": false, + "_comment": " model parameters", + "use_smooth": false, + "sel_a": [16, 32], + "sel_r": [30, 60], + "rcut": 6.00, + "axis_rule": [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0], + "_comment": " default rule: []", + "_comment": " user defined rule: for each type provides two axes, ", + "_comment": " for each axis: (a_or_r, type, idx)", + "_comment": " if type < 0, exclude type -(type+1)", + "_comment": " for water (O:0, H:1) it can be", + "_comment": " [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0]", + "fitting_neuron": [240, 120, 60, 30, 10], + + "_comment": " traing controls", + "systems": ["system"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 4, + "start_lr": 0.001, + "decay_steps": 5000, + "decay_rate": 0.95, + + "start_pref_e": 0.02, + "limit_pref_e": 8, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + + "seed": 1, + + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 1, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training": true, + "time_training": true, + + "_comment": "that's all" +} + diff --git a/source/tests/water_smth.json b/source/tests/water_smth.json new file mode 100644 index 0000000000..4b5ced3cd6 --- /dev/null +++ b/source/tests/water_smth.json @@ -0,0 +1,48 @@ +{ + "_comment": " model parameters", + "use_smooth": true, + "sel_a": [46, 92], + "rcut_smth": 5.80, + "rcut": 6.00, + "filter_neuron": [25, 50, 100], + "filter_resnet_dt": false, + "axis_neuron": 16, + "fitting_neuron": [240, 240, 240], + "fitting_resnet_dt":true, + "coord_norm": true, + "type_fitting_net": false, + + "_comment": " traing controls", + "systems": ["system"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 1, + "start_lr": 0.005, + "decay_steps": 5000, + "decay_rate": 0.95, + + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + + "seed": 1, + + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 1, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training": true, + "time_training": true, + "profiling": false, + "profiling_file": "timeline.json", + + "_comment": "that's all" +} + diff --git a/source/train/ModelLocFrame.py b/source/train/ModelLocFrame.py new file mode 100644 index 0000000000..ba1eebfc11 --- /dev/null +++ b/source/train/ModelLocFrame.py @@ -0,0 +1,367 @@ +import os +import numpy as np +import tensorflow as tf +from deepmd.common import j_must_have, j_must_have_d, j_have +from deepmd.Model import Model + +from deepmd.RunOptions import global_tf_float_precision +from deepmd.RunOptions import global_np_float_precision +from deepmd.RunOptions import global_ener_float_precision +from deepmd.RunOptions import global_cvt_2_tf_float +from deepmd.RunOptions import global_cvt_2_ener_float + +module_path = os.path.dirname(os.path.realpath(__file__)) + "/" +assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" +op_module = tf.load_op_library(module_path + "libop_abi.so") + +class ModelLocFrame(Model) : + def __init__(self, jdata) : + # descrpt config + self.sel_a = j_must_have (jdata, 'sel_a') + self.sel_r = j_must_have (jdata, 'sel_r') + self.ntypes = len(self.sel_a) + assert(self.ntypes == len(self.sel_r)) + self.rcut_a = -1 + self.rcut_r = j_must_have (jdata, 'rcut') + # axis + self.axis_rule = j_must_have (jdata, 'axis_rule') + # fparam + self.numb_fparam = 0 + if j_have(jdata, 'numb_fparam') : + self.numb_fparam = jdata['numb_fparam'] + # type_map + self.type_map = [] + if j_have(jdata, 'type_map') : + self.numb_fparam = jdata['type_map'] + # norm coord + if j_have(jdata, 'coord_norm') : + self.coord_norm = jdata['coord_norm'] + else : + self.coord_norm = True + # numb of neighbors and numb of descrptors + self.nnei_a = np.cumsum(self.sel_a)[-1] + self.nnei_r = np.cumsum(self.sel_r)[-1] + self.nnei = self.nnei_a + self.nnei_r + self.ndescrpt_a = self.nnei_a * 4 + self.ndescrpt_r = self.nnei_r * 1 + self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r + # network size + self.n_neuron = j_must_have_d (jdata, 'fitting_neuron', ['n_neuron']) + self.resnet_dt = True + if j_have(jdata, 'resnet_dt') : + warnings.warn("the key \"%s\" is deprecated, please use \"%s\" instead" % ('resnet_dt','fitting_resnet_dt')) + self.resnet_dt = jdata['resnet_dt'] + if j_have(jdata, 'fitting_resnet_dt') : + self.resnet_dt = jdata['fitting_resnet_dt'] + # short-range tab + if 'use_srtab' in jdata : + self.srtab = TabInter(jdata['use_srtab']) + self.smin_alpha = j_must_have(jdata, 'smin_alpha') + self.sw_rmin = j_must_have(jdata, 'sw_rmin') + self.sw_rmax = j_must_have(jdata, 'sw_rmax') + else : + self.srtab = None + + self.seed = None + if j_have (jdata, 'seed') : + self.seed = jdata['seed'] + self.useBN = False + + def compute_dstats (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + all_davg = [] + all_dstd = [] + if True: + sumv = [] + sumn = [] + sumv2 = [] + for cc,bb,tt,nn,mm in zip(data_coord,data_box,data_atype,natoms_vec,mesh) : + sysv,sysv2,sysn \ + = self._compute_dstats_sys_nonsmth(cc,bb,tt,nn,mm,reuse) + sumv.append(sysv) + sumn.append(sysn) + sumv2.append(sysv2) + sumv = np.sum(sumv, axis = 0) + sumn = np.sum(sumn, axis = 0) + sumv2 = np.sum(sumv2, axis = 0) + for type_i in range(self.ntypes) : + davg = sumv[type_i] / sumn[type_i] + dstd = self._compute_std(sumv2[type_i], sumv[type_i], sumn[type_i]) + for ii in range (len(dstd)) : + if (np.abs(dstd[ii]) < 1e-2) : + dstd[ii] = 1e-2 + all_davg.append(davg) + all_dstd.append(dstd) + davg = np.array(all_davg) + dstd = np.array(all_dstd) + return davg, dstd + + + def build_interaction (self, + coord_, + atype_, + natoms, + box, + mesh, + fparam, + davg = None, + dstd = None, + bias_atom_e = None, + suffix = '', + reuse_attr = None, + reuse_weights = None): + with tf.variable_scope('model_attr' + suffix, reuse = reuse_attr) : + if davg is None: + davg = np.zeros([self.ntypes, self.ndescrpt]) + if dstd is None: + dstd = np.ones ([self.ntypes, self.ndescrpt]) + t_rcut = tf.constant(np.max([self.rcut_r, self.rcut_a]), + name = 'rcut', + dtype = global_tf_float_precision) + t_ntypes = tf.constant(self.ntypes, + name = 'ntypes', + dtype = tf.int32) + t_dfparam = tf.constant(self.numb_fparam, + name = 'dfparam', + dtype = tf.int32) + t_tmap = tf.constant(' '.join(self.type_map), + name = 'tmap', + dtype = tf.string) + self.t_avg = tf.get_variable('t_avg', + davg.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(davg, dtype = global_tf_float_precision)) + self.t_std = tf.get_variable('t_std', + dstd.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(dstd, dtype = global_tf_float_precision)) + if self.srtab is not None : + tab_info, tab_data = self.srtab.get() + self.tab_info = tf.get_variable('t_tab_info', + tab_info.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) + self.tab_data = tf.get_variable('t_tab_data', + tab_data.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) + + coord = tf.reshape (coord_, [-1, natoms[1] * 3]) + atype = tf.reshape (atype_, [-1, natoms[1]]) + + descrpt, descrpt_deriv, rij, nlist, axis \ + = op_module.descrpt (coord, + atype, + natoms, + box, + mesh, + self.t_avg, + self.t_std, + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + sel_a = self.sel_a, + sel_r = self.sel_r, + axis_rule = self.axis_rule) + + descrpt_reshape = tf.reshape(descrpt, [-1, self.ndescrpt]) + + atom_ener = self.build_atom_net (descrpt_reshape, fparam, natoms, bias_atom_e = bias_atom_e, reuse = reuse_weights) + + if self.srtab is not None : + sw_lambda, sw_deriv \ + = op_module.soft_min_switch(atype, + rij, + nlist, + natoms, + sel_a = self.sel_a, + sel_r = self.sel_r, + alpha = self.smin_alpha, + rmin = self.sw_rmin, + rmax = self.sw_rmax) + inv_sw_lambda = 1.0 - sw_lambda + # NOTICE: + # atom energy is not scaled, + # force and virial are scaled + tab_atom_ener, tab_force, tab_atom_virial \ + = op_module.tab_inter(self.tab_info, + self.tab_data, + atype, + rij, + nlist, + natoms, + sw_lambda, + sel_a = self.sel_a, + sel_r = self.sel_r) + energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, natoms[0]]) + tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) + atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener + energy_raw = tab_atom_ener + atom_ener + else : + energy_raw = atom_ener + + energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'o_atom_energy'+suffix) + energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='o_energy'+suffix) + + net_deriv_tmp = tf.gradients (atom_ener, descrpt_reshape) + net_deriv = net_deriv_tmp[0] + net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) + + force = op_module.prod_force (net_deriv_reshape, + descrpt_deriv, + nlist, + axis, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + if self.srtab is not None : + sw_force \ + = op_module.soft_min_force(energy_diff, + sw_deriv, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + force = force + sw_force + tab_force + + force = tf.reshape (force, [-1, 3 * natoms[1]], name = "o_force"+suffix) + + virial, atom_virial \ + = op_module.prod_virial (net_deriv_reshape, + descrpt_deriv, + rij, + nlist, + axis, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + if self.srtab is not None : + sw_virial, sw_atom_virial \ + = op_module.soft_min_virial (energy_diff, + sw_deriv, + rij, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + atom_virial = atom_virial + sw_atom_virial + tab_atom_virial + virial = virial + sw_virial \ + + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, natoms[1], 9]), axis = 1) + + virial = tf.reshape (virial, [-1, 9], name = "o_virial"+suffix) + atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "o_atom_virial"+suffix) + + return energy, force, virial, energy_raw + + + def build_atom_net (self, + inputs, + fparam, + natoms, + bias_atom_e = None, + reuse = None) : + start_index = 0 + inputs = tf.reshape(inputs, [-1, self.ndescrpt * natoms[0]]) + shape = inputs.get_shape().as_list() + if bias_atom_e is not None : + assert(len(bias_atom_e) == self.ntypes) + + for type_i in range(self.ntypes): + # cut-out inputs + inputs_i = tf.slice (inputs, + [ 0, start_index* self.ndescrpt], + [-1, natoms[2+type_i]* self.ndescrpt] ) + inputs_i = tf.reshape(inputs_i, [-1, self.ndescrpt]) + start_index += natoms[2+type_i] + if bias_atom_e is None : + type_bias_ae = 0.0 + else : + type_bias_ae = bias_atom_e[type_i] + + # compute atom energy + if self.numb_fparam > 0 : + ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) + ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) + ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) + layer = tf.concat([layer, ext_fparam], axis = 1) + layer = self.one_layer(inputs_i, self.n_neuron[0], name='layer_0_type_'+str(type_i), reuse=reuse, seed = self.seed) + for ii in range(1,len(self.n_neuron)) : + layer = self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i), reuse=reuse, seed = self.seed) + + final_layer = self.one_layer(layer, 1, activation_fn = None, bavg = type_bias_ae, name='final_layer_type_'+str(type_i), reuse=reuse, seed = self.seed) + final_layer = tf.reshape(final_layer, [-1, natoms[2+type_i]]) + # final_layer = tf.cond (tf.equal(natoms[2+type_i], 0), lambda: tf.zeros((0, 0), dtype=global_tf_float_precision), lambda : tf.reshape(final_layer, [-1, natoms[2+type_i]])) + + # concat the results + if type_i == 0: + outs = final_layer + else: + outs = tf.concat([outs, final_layer], axis = 1) + + return tf.reshape(outs, [-1]) + + + def _compute_dstats_sys_nonsmth (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + avg_zero = np.zeros([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) + std_ones = np.ones ([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) + sub_graph = tf.Graph() + with sub_graph.as_default(): + descrpt, descrpt_deriv, rij, nlist, axis \ + = op_module.descrpt (tf.constant(data_coord), + tf.constant(data_atype), + tf.constant(natoms_vec, dtype = tf.int32), + tf.constant(data_box), + tf.constant(mesh), + tf.constant(avg_zero), + tf.constant(std_ones), + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + sel_a = self.sel_a, + sel_r = self.sel_r, + axis_rule = self.axis_rule) + # self.sess.run(tf.global_variables_initializer()) + # sub_sess = tf.Session(graph = sub_graph, + # config=tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, + # inter_op_parallelism_threads=self.run_opt.num_inter_threads + # )) + sub_sess = tf.Session(graph = sub_graph) + dd_all = sub_sess.run(descrpt) + sub_sess.close() + natoms = natoms_vec + dd_all = np.reshape(dd_all, [-1, self.ndescrpt * natoms[0]]) + start_index = 0 + sysv = [] + sysn = [] + sysv2 = [] + for type_i in range(self.ntypes): + end_index = start_index + self.ndescrpt * natoms[2+type_i] + dd = dd_all[:, start_index:end_index] + dd = np.reshape(dd, [-1, self.ndescrpt]) + start_index = end_index + # compute + sumv = np.sum(dd, axis = 0) + sumn = dd.shape[0] + sumv2 = np.sum(np.multiply(dd,dd), axis = 0) + sysv.append(sumv) + sysn.append(sumn) + sysv2.append(sumv2) + return sysv, sysv2, sysn + + + def _compute_std (self,sumv2, sumv, sumn) : + return np.sqrt(sumv2/sumn - np.multiply(sumv/sumn, sumv/sumn)) + diff --git a/source/train/ModelSeA.py b/source/train/ModelSeA.py new file mode 100644 index 0000000000..f921f5be37 --- /dev/null +++ b/source/train/ModelSeA.py @@ -0,0 +1,574 @@ +import os,warnings +import numpy as np +import tensorflow as tf +from deepmd.common import j_must_have, j_must_have_d, j_have +from deepmd.Model import Model + +from deepmd.RunOptions import global_tf_float_precision +from deepmd.RunOptions import global_np_float_precision +from deepmd.RunOptions import global_ener_float_precision +from deepmd.RunOptions import global_cvt_2_tf_float +from deepmd.RunOptions import global_cvt_2_ener_float + +module_path = os.path.dirname(os.path.realpath(__file__)) + "/" +assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" +op_module = tf.load_op_library(module_path + "libop_abi.so") + +class ModelSeA (Model): + def __init__ (self, jdata): + # descrpt config + self.use_smooth = False + self.sel_a = j_must_have (jdata, 'sel_a') + self.sel_r = [ 0 for ii in range(len(self.sel_a)) ] + if j_have (jdata, 'sel_r') : + warnings.warn ('ignoring key sel_r in the json database and set sel_r to %s' % str(self.sel_r)) + self.ntypes = len(self.sel_a) + assert(self.ntypes == len(self.sel_r)) + self.rcut_a = -1 + self.rcut_r = j_must_have (jdata, 'rcut') + if j_have(jdata, 'rcut_smth') : + self.rcut_r_smth = jdata['rcut_smth'] + else : + self.rcut_r_smth = self.rcut_r + # fparam + self.numb_fparam = 0 + if j_have(jdata, 'numb_fparam') : + self.numb_fparam = jdata['numb_fparam'] + # type_map + self.type_map = [] + if j_have(jdata, 'type_map') : + self.numb_fparam = jdata['type_map'] + # filter of smooth version + if j_have(jdata, 'coord_norm') : + self.coord_norm = jdata['coord_norm'] + else : + self.coord_norm = True + self.filter_neuron = j_must_have (jdata, 'filter_neuron') + self.n_axis_neuron = j_must_have_d (jdata, 'axis_neuron', ['n_axis_neuron']) + self.filter_resnet_dt = False + if j_have(jdata, 'filter_resnet_dt') : + self.filter_resnet_dt = jdata['filter_resnet_dt'] + # numb of neighbors and numb of descrptors + self.nnei_a = np.cumsum(self.sel_a)[-1] + self.nnei_r = np.cumsum(self.sel_r)[-1] + self.nnei = self.nnei_a + self.nnei_r + self.ndescrpt_a = self.nnei_a * 4 + self.ndescrpt_r = self.nnei_r * 1 + self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r + # network size + self.n_neuron = j_must_have_d (jdata, 'fitting_neuron', ['n_neuron']) + self.resnet_dt = True + if j_have(jdata, 'resnet_dt') : + warnings.warn("the key \"%s\" is deprecated, please use \"%s\" instead" % ('resnet_dt','fitting_resnet_dt')) + self.resnet_dt = jdata['resnet_dt'] + if j_have(jdata, 'fitting_resnet_dt') : + self.resnet_dt = jdata['fitting_resnet_dt'] + if j_have(jdata, 'type_fitting_net') : + self.type_fitting_net = jdata['type_fitting_net'] + else : + self.type_fitting_net = False + + # short-range tab + if 'use_srtab' in jdata : + self.srtab = TabInter(jdata['use_srtab']) + self.smin_alpha = j_must_have(jdata, 'smin_alpha') + self.sw_rmin = j_must_have(jdata, 'sw_rmin') + self.sw_rmax = j_must_have(jdata, 'sw_rmax') + else : + self.srtab = None + + self.seed = None + if j_have (jdata, 'seed') : + self.seed = jdata['seed'] + self.useBN = False + + + def compute_dstats (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + all_davg = [] + all_dstd = [] + if True: + sumr = [] + suma = [] + sumn = [] + sumr2 = [] + suma2 = [] + for cc,bb,tt,nn,mm in zip(data_coord,data_box,data_atype,natoms_vec,mesh) : + sysr,sysr2,sysa,sysa2,sysn \ + = self._compute_dstats_sys_smth(cc,bb,tt,nn,mm,reuse) + sumr.append(sysr) + suma.append(sysa) + sumn.append(sysn) + sumr2.append(sysr2) + suma2.append(sysa2) + sumr = np.sum(sumr, axis = 0) + suma = np.sum(suma, axis = 0) + sumn = np.sum(sumn, axis = 0) + sumr2 = np.sum(sumr2, axis = 0) + suma2 = np.sum(suma2, axis = 0) + for type_i in range(self.ntypes) : + davgunit = [sumr[type_i]/sumn[type_i], 0, 0, 0] + dstdunit = [self._compute_std(sumr2[type_i], sumr[type_i], sumn[type_i]), + self._compute_std(suma2[type_i], suma[type_i], sumn[type_i]), + self._compute_std(suma2[type_i], suma[type_i], sumn[type_i]), + self._compute_std(suma2[type_i], suma[type_i], sumn[type_i]) + ] + davg = np.tile(davgunit, self.ndescrpt // 4) + dstd = np.tile(dstdunit, self.ndescrpt // 4) + all_davg.append(davg) + all_dstd.append(dstd) + + davg = np.array(all_davg) + dstd = np.array(all_dstd) + + return davg, dstd + + + def build_interaction (self, + coord_, + atype_, + natoms, + box, + mesh, + fparam, + davg = None, + dstd = None, + bias_atom_e = None, + suffix = '', + reuse_attr = None, + reuse_weights = None): + with tf.variable_scope('model_attr' + suffix, reuse = reuse_attr) : + if davg is None: + davg = np.zeros([self.ntypes, self.ndescrpt]) + if dstd is None: + dstd = np.ones ([self.ntypes, self.ndescrpt]) + t_rcut = tf.constant(np.max([self.rcut_r, self.rcut_a]), + name = 'rcut', + dtype = global_tf_float_precision) + t_ntypes = tf.constant(self.ntypes, + name = 'ntypes', + dtype = tf.int32) + t_dfparam = tf.constant(self.numb_fparam, + name = 'dfparam', + dtype = tf.int32) + t_tmap = tf.constant(' '.join(self.type_map), + name = 'tmap', + dtype = tf.string) + self.t_avg = tf.get_variable('t_avg', + davg.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(davg, dtype = global_tf_float_precision)) + self.t_std = tf.get_variable('t_std', + dstd.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(dstd, dtype = global_tf_float_precision)) + if self.srtab is not None : + tab_info, tab_data = self.srtab.get() + self.tab_info = tf.get_variable('t_tab_info', + tab_info.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) + self.tab_data = tf.get_variable('t_tab_data', + tab_data.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) + + coord = tf.reshape (coord_, [-1, natoms[1] * 3]) + atype = tf.reshape (atype_, [-1, natoms[1]]) + + descrpt, descrpt_deriv, rij, nlist \ + = op_module.descrpt_norot (coord, + atype, + natoms, + box, + mesh, + self.t_avg, + self.t_std, + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + rcut_r_smth = self.rcut_r_smth, + sel_a = self.sel_a, + sel_r = self.sel_r) + + descrpt_reshape = tf.reshape(descrpt, [-1, self.ndescrpt]) + + atom_ener = self.build_atom_net (descrpt_reshape, + fparam, + natoms, + bias_atom_e = bias_atom_e, + reuse = reuse_weights, + suffix = suffix) + + if self.srtab is not None : + sw_lambda, sw_deriv \ + = op_module.soft_min_switch(atype, + rij, + nlist, + natoms, + sel_a = self.sel_a, + sel_r = self.sel_r, + alpha = self.smin_alpha, + rmin = self.sw_rmin, + rmax = self.sw_rmax) + inv_sw_lambda = 1.0 - sw_lambda + # NOTICE: + # atom energy is not scaled, + # force and virial are scaled + tab_atom_ener, tab_force, tab_atom_virial \ + = op_module.tab_inter(self.tab_info, + self.tab_data, + atype, + rij, + nlist, + natoms, + sw_lambda, + sel_a = self.sel_a, + sel_r = self.sel_r) + energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, natoms[0]]) + tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) + atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener + energy_raw = tab_atom_ener + atom_ener + else : + energy_raw = atom_ener + + energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'o_atom_energy'+suffix) + energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='o_energy'+suffix) + + net_deriv_tmp = tf.gradients (atom_ener, descrpt_reshape) + net_deriv = net_deriv_tmp[0] + net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) + + force = op_module.prod_force_norot (net_deriv_reshape, + descrpt_deriv, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + if self.srtab is not None : + sw_force \ + = op_module.soft_min_force(energy_diff, + sw_deriv, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + force = force + sw_force + tab_force + + force = tf.reshape (force, [-1, 3 * natoms[1]], name = "o_force"+suffix) + + virial, atom_virial \ + = op_module.prod_virial_norot (net_deriv_reshape, + descrpt_deriv, + rij, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + if self.srtab is not None : + sw_virial, sw_atom_virial \ + = op_module.soft_min_virial (energy_diff, + sw_deriv, + rij, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + atom_virial = atom_virial + sw_atom_virial + tab_atom_virial + virial = virial + sw_virial \ + + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, natoms[1], 9]), axis = 1) + + virial = tf.reshape (virial, [-1, 9], name = "o_virial"+suffix) + atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "o_atom_virial"+suffix) + + return energy, force, virial, energy_raw + + + + def build_atom_net (self, + inputs, + fparam, + natoms, + bias_atom_e = None, + reuse = None, + suffix = '') : + start_index = 0 + inputs = tf.reshape(inputs, [-1, self.ndescrpt * natoms[0]]) + shape = inputs.get_shape().as_list() + if bias_atom_e is not None : + assert(len(bias_atom_e) == self.ntypes) + + for type_i in range(self.ntypes): + # cut-out inputs + inputs_i = tf.slice (inputs, + [ 0, start_index* self.ndescrpt], + [-1, natoms[2+type_i]* self.ndescrpt] ) + inputs_i = tf.reshape(inputs_i, [-1, self.ndescrpt]) + start_index += natoms[2+type_i] + if bias_atom_e is None : + type_bias_ae = 0.0 + else : + type_bias_ae = bias_atom_e[type_i] + + # compute atom energy + if self.type_fitting_net : + layer = self._filter_type_ext(inputs_i, name='filter_type_'+str(type_i)+suffix, natoms=natoms, reuse=reuse, seed = self.seed) + else : + layer = self._filter(inputs_i, name='filter_type_'+str(type_i)+suffix, natoms=natoms, reuse=reuse, seed = self.seed) + if self.numb_fparam > 0 : + ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) + ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) + ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) + layer = tf.concat([layer, ext_fparam], axis = 1) + for ii in range(0,len(self.n_neuron)) : + if ii >= 1 and self.n_neuron[ii] == self.n_neuron[ii-1] : + layer+= self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed, use_timestep = self.resnet_dt) + else : + layer = self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) + final_layer = self.one_layer(layer, 1, activation_fn = None, bavg = type_bias_ae, name='final_layer_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) + final_layer = tf.reshape(final_layer, [-1, natoms[2+type_i]]) + # final_layer = tf.cond (tf.equal(natoms[2+type_i], 0), lambda: tf.zeros((0, 0), dtype=global_tf_float_precision), lambda : tf.reshape(final_layer, [-1, natoms[2+type_i]])) + + # concat the results + if type_i == 0: + outs = final_layer + else: + outs = tf.concat([outs, final_layer], axis = 1) + + return tf.reshape(outs, [-1]) + + + def _compute_dstats_sys_smth (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + avg_zero = np.zeros([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) + std_ones = np.ones ([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) + sub_graph = tf.Graph() + with sub_graph.as_default(): + descrpt, descrpt_deriv, rij, nlist \ + = op_module.descrpt_norot (tf.constant(data_coord), + tf.constant(data_atype), + tf.constant(natoms_vec, dtype = tf.int32), + tf.constant(data_box), + tf.constant(mesh), + tf.constant(avg_zero), + tf.constant(std_ones), + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + rcut_r_smth = self.rcut_r_smth, + sel_a = self.sel_a, + sel_r = self.sel_r) + # self.sess.run(tf.global_variables_initializer()) + # sub_sess = tf.Session(graph = sub_graph, + # config=tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, + # inter_op_parallelism_threads=self.run_opt.num_inter_threads + + # )) + sub_sess = tf.Session(graph = sub_graph) + dd_all = sub_sess.run(descrpt) + sub_sess.close() + natoms = natoms_vec + dd_all = np.reshape(dd_all, [-1, self.ndescrpt * natoms[0]]) + start_index = 0 + sysr = [] + sysa = [] + sysn = [] + sysr2 = [] + sysa2 = [] + for type_i in range(self.ntypes): + end_index = start_index + self.ndescrpt * natoms[2+type_i] + dd = dd_all[:, start_index:end_index] + dd = np.reshape(dd, [-1, self.ndescrpt]) + start_index = end_index + # compute + dd = np.reshape (dd, [-1, 4]) + ddr = dd[:,:1] + dda = dd[:,1:] + sumr = np.sum(ddr) + suma = np.sum(dda) / 3. + sumn = dd.shape[0] + sumr2 = np.sum(np.multiply(ddr, ddr)) + suma2 = np.sum(np.multiply(dda, dda)) / 3. + sysr.append(sumr) + sysa.append(suma) + sysn.append(sumn) + sysr2.append(sumr2) + sysa2.append(suma2) + return sysr, sysr2, sysa, sysa2, sysn + + + def _compute_std (self,sumv2, sumv, sumn) : + return np.sqrt(sumv2/sumn - np.multiply(sumv/sumn, sumv/sumn)) + + + def _filter(self, + inputs, + natoms, + activation_fn=tf.nn.tanh, + stddev=1.0, + bavg=0.0, + name='linear', + reuse=None, + seed=None): + # natom x (nei x 4) + shape = inputs.get_shape().as_list() + outputs_size = [1] + self.filter_neuron + outputs_size_2 = self.n_axis_neuron + with tf.variable_scope(name, reuse=reuse): + start_index = 0 + xyz_scatter_total = [] + for type_i in range(self.ntypes): + # cut-out inputs + # with natom x (nei_type_i x 4) + inputs_i = tf.slice (inputs, + [ 0, start_index* 4], + [-1, self.sel_a[type_i]* 4] ) + start_index += self.sel_a[type_i] + shape_i = inputs_i.get_shape().as_list() + # with (natom x nei_type_i) x 4 + inputs_reshape = tf.reshape(inputs_i, [-1, 4]) + xyz_scatter = tf.reshape(tf.slice(inputs_reshape, [0,0],[-1,1]),[-1,1]) + for ii in range(1, len(outputs_size)): + w = tf.get_variable('matrix_'+str(ii)+'_'+str(type_i), + [outputs_size[ii - 1], outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev/np.sqrt(outputs_size[ii]+outputs_size[ii-1]), seed = seed)) + b = tf.get_variable('bias_'+str(ii)+'_'+str(type_i), + [1, outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev, mean = bavg, seed = seed)) + if self.filter_resnet_dt : + idt = tf.get_variable('idt_'+str(ii)+'_'+str(type_i), + [1, outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=0.001, mean = 1.0, seed = seed)) + if outputs_size[ii] == outputs_size[ii-1]: + if self.filter_resnet_dt : + xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) * idt + else : + xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) + elif outputs_size[ii] == outputs_size[ii-1] * 2: + if self.filter_resnet_dt : + xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) * idt + else : + xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) + else: + xyz_scatter = activation_fn(tf.matmul(xyz_scatter, w) + b) + # natom x nei_type_i x out_size + xyz_scatter = tf.reshape(xyz_scatter, (-1, shape_i[1]//4, outputs_size[-1])) + xyz_scatter_total.append(xyz_scatter) + + # natom x nei x outputs_size + xyz_scatter = tf.concat(xyz_scatter_total, axis=1) + # natom x nei x 4 + inputs_reshape = tf.reshape(inputs, [-1, shape[1]//4, 4]) + # natom x 4 x outputs_size + xyz_scatter_1 = tf.matmul(inputs_reshape, xyz_scatter, transpose_a = True) + xyz_scatter_1 = xyz_scatter_1 * (4.0 / shape[1]) + # natom x 4 x outputs_size_2 + xyz_scatter_2 = tf.slice(xyz_scatter_1, [0,0,0],[-1,-1,outputs_size_2]) + # natom x outputs_size x outputs_size_2 + result = tf.matmul(xyz_scatter_1, xyz_scatter_2, transpose_a = True) + # natom x (outputs_size x outputs_size_2) + result = tf.reshape(result, [-1, outputs_size_2 * outputs_size[-1]]) + + return result + + def _filter_type_ext(self, + inputs, + natoms, + activation_fn=tf.nn.tanh, + stddev=1.0, + bavg=0.0, + name='linear', + reuse=None, + seed=None): + # natom x (nei x 4) + shape = inputs.get_shape().as_list() + outputs_size = [1] + self.filter_neuron + outputs_size_2 = self.n_axis_neuron + with tf.variable_scope(name, reuse=reuse): + start_index = 0 + result_all = [] + xyz_scatter_1_all = [] + xyz_scatter_2_all = [] + for type_i in range(self.ntypes): + # cut-out inputs + # with natom x (nei_type_i x 4) + inputs_i = tf.slice (inputs, + [ 0, start_index* 4], + [-1, self.sel_a[type_i]* 4] ) + start_index += self.sel_a[type_i] + shape_i = inputs_i.get_shape().as_list() + # with (natom x nei_type_i) x 4 + inputs_reshape = tf.reshape(inputs_i, [-1, 4]) + xyz_scatter = tf.reshape(tf.slice(inputs_reshape, [0,0],[-1,1]),[-1,1]) + for ii in range(1, len(outputs_size)): + w = tf.get_variable('matrix_'+str(ii)+'_'+str(type_i), + [outputs_size[ii - 1], outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev/np.sqrt(outputs_size[ii]+outputs_size[ii-1]), seed = seed)) + b = tf.get_variable('bias_'+str(ii)+'_'+str(type_i), + [1, outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev, mean = bavg, seed = seed)) + if self.filter_resnet_dt : + idt = tf.get_variable('idt_'+str(ii)+'_'+str(type_i), + [1, outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=0.001, mean = 1.0, seed = seed)) + if outputs_size[ii] == outputs_size[ii-1]: + if self.filter_resnet_dt : + xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) * idt + else : + xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) + elif outputs_size[ii] == outputs_size[ii-1] * 2: + if self.filter_resnet_dt : + xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) * idt + else : + xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) + else: + xyz_scatter = activation_fn(tf.matmul(xyz_scatter, w) + b) + # natom x nei_type_i x out_size + xyz_scatter = tf.reshape(xyz_scatter, (-1, shape_i[1]//4, outputs_size[-1])) + # natom x nei_type_i x 4 + inputs_i_reshape = tf.reshape(inputs_i, [-1, shape_i[1]//4, 4]) + # natom x 4 x outputs_size + xyz_scatter_1 = tf.matmul(inputs_i_reshape, xyz_scatter, transpose_a = True) + xyz_scatter_1 = xyz_scatter_1 * (4.0 / shape_i[1]) + # natom x 4 x outputs_size_2 + xyz_scatter_2 = tf.slice(xyz_scatter_1, [0,0,0],[-1,-1,outputs_size_2]) + xyz_scatter_1_all.append(xyz_scatter_1) + xyz_scatter_2_all.append(xyz_scatter_2) + + # for type_i in range(self.ntypes): + # for type_j in range(type_i, self.ntypes): + # # natom x outputs_size x outputs_size_2 + # result = tf.matmul(xyz_scatter_1_all[type_i], xyz_scatter_2_all[type_j], transpose_a = True) + # # natom x (outputs_size x outputs_size_2) + # result = tf.reshape(result, [-1, outputs_size_2 * outputs_size[-1]]) + # result_all.append(tf.identity(result)) + xyz_scatter_2_coll = tf.concat(xyz_scatter_2_all, axis = 2) + for type_i in range(self.ntypes) : + # natom x outputs_size x (outputs_size_2 x ntypes) + result = tf.matmul(xyz_scatter_1_all[type_i], xyz_scatter_2_coll, transpose_a = True) + # natom x (outputs_size x outputs_size_2 x ntypes) + result = tf.reshape(result, [-1, outputs_size_2 * self.ntypes * outputs_size[-1]]) + result_all.append(tf.identity(result)) + + # natom x (ntypes x outputs_size x outputs_size_2 x ntypes) + result_all = tf.concat(result_all, axis = 1) + + return result_all diff --git a/source/train/common.py b/source/train/common.py new file mode 100644 index 0000000000..db53d882a5 --- /dev/null +++ b/source/train/common.py @@ -0,0 +1,20 @@ +def j_must_have (jdata, key) : + if not key in jdata.keys() : + raise RuntimeError ("json database must provide key " + key ) + else : + return jdata[key] + +def j_must_have_d (jdata, key, deprecated_key) : + if not key in jdata.keys() : + # raise RuntimeError ("json database must provide key " + key ) + for ii in deprecated_key : + if ii in jdata.keys() : + warnings.warn("the key \"%s\" is deprecated, please use \"%s\" instead" % (ii,key)) + return jdata[ii] + raise RuntimeError ("json database must provide key " + key ) + else : + return jdata[key] + +def j_have (jdata, key) : + return key in jdata.keys() + From a8743583d3487d9f8af3bfd9f1c0fec2787ceb9f Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 21 Jun 2019 21:16:03 +0800 Subject: [PATCH 09/33] split model and trainer --- source/train/CMakeLists.txt | 2 +- source/train/Model.py | 1194 +---------------------------------- source/train/Trainer.py | 505 +++++++++++++++ source/train/train.py | 6 +- 4 files changed, 527 insertions(+), 1180 deletions(-) create mode 100644 source/train/Trainer.py diff --git a/source/train/CMakeLists.txt b/source/train/CMakeLists.txt index 02eded5f84..21471b76f4 100644 --- a/source/train/CMakeLists.txt +++ b/source/train/CMakeLists.txt @@ -2,7 +2,7 @@ configure_file("RunOptions.py.in" "${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py" @ONLY) -file(GLOB LIB_PY common.py DeepPot.py Data.py DataSystem.py Model*.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) +file(GLOB LIB_PY common.py DeepPot.py Data.py DataSystem.py Model*.py Trainer.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) file(GLOB CLS_PY Local.py Slurm.py) diff --git a/source/train/Model.py b/source/train/Model.py index 2decf4cc9f..72e45c6960 100644 --- a/source/train/Model.py +++ b/source/train/Model.py @@ -1,219 +1,14 @@ -#!/usr/bin/env python3 -import os -import sys -import time -import shutil -import warnings import numpy as np import tensorflow as tf + from deepmd.RunOptions import global_tf_float_precision from deepmd.RunOptions import global_np_float_precision from deepmd.RunOptions import global_ener_float_precision from deepmd.RunOptions import global_cvt_2_tf_float from deepmd.RunOptions import global_cvt_2_ener_float -# from ModelSeA import ModelSeA -# from ModelLocFrame import ModelLocFrame - -from tensorflow.python.framework import ops -from tensorflow.python.client import timeline - -# load force module -module_path = os.path.dirname(os.path.realpath(__file__)) + "/" -assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" -op_module = tf.load_op_library(module_path + "libop_abi.so") - -# load grad of force module -sys.path.append (module_path ) -import deepmd._prod_force_grad -import deepmd._prod_virial_grad -import deepmd._prod_force_norot_grad -import deepmd._prod_virial_norot_grad -import deepmd._soft_min_force_grad -import deepmd._soft_min_virial_grad -from deepmd.RunOptions import RunOptions -from deepmd.TabInter import TabInter - -from deepmd.common import j_must_have, j_must_have_d, j_have - -def _is_subdir(path, directory): - path = os.path.realpath(path) - directory = os.path.realpath(directory) - if path == directory: - return False - relative = os.path.relpath(path, directory) + os.sep - return not relative.startswith(os.pardir + os.sep) - -class LearingRate (object) : - def __init__ (self, - jdata, - tot_numb_batches) : - self.decay_steps_ = j_must_have(jdata, 'decay_steps') - self.decay_rate_ = j_must_have(jdata, 'decay_rate') - self.start_lr_ = j_must_have(jdata, 'start_lr') - self.tot_numb_batches = tot_numb_batches - - def value (self, - batch) : - return self.start_lr_ * np.power (self.decay_rate_, (batch // self.decay_steps())) - - def decay_steps (self) : -# return self.decay_steps_ * self.tot_numb_batches - return self.decay_steps_ - - def decay_rate (self) : - return self.decay_rate_ - - def start_lr (self) : - return self.start_lr_ - -class NNPModel (object): - def __init__(self, - jdata, - run_opt): - self.run_opt = run_opt - self._init_param(jdata) - - def _init_param(self, jdata): - # descrpt config - self.use_smooth = False - if j_have (jdata, "use_smooth") : - self.use_smooth = jdata["use_smooth"] - self.sel_a = j_must_have (jdata, 'sel_a') - self.sel_r = [ 0 for ii in range(len(self.sel_a)) ] - if not self.use_smooth : - self.sel_r = j_must_have (jdata, 'sel_r') - else : - if j_have (jdata, 'sel_r') : - warnings.warn ('ignoring key sel_r in the json database and set sel_r to %s' % str(self.sel_r)) - self.rcut_a = -1 - self.rcut_r = j_must_have (jdata, 'rcut') - if j_have(jdata, 'rcut_smth') : - self.rcut_r_smth = jdata['rcut_smth'] - else : - self.rcut_r_smth = self.rcut_r - # axis - self.axis_rule = [] - if j_have (jdata, 'axis_rule') : - self.axis_rule = jdata['axis_rule'] - # filter of smooth version - if self.use_smooth : - if j_have(jdata, 'coord_norm') : - self.coord_norm = jdata['coord_norm'] - else : - self.coord_norm = True - self.filter_neuron = j_must_have (jdata, 'filter_neuron') - self.n_axis_neuron = j_must_have_d (jdata, 'axis_neuron', ['n_axis_neuron']) - self.filter_resnet_dt = False - if j_have(jdata, 'filter_resnet_dt') : - self.filter_resnet_dt = jdata['filter_resnet_dt'] - # numb of neighbors and numb of descrptors - self.nnei_a = np.cumsum(self.sel_a)[-1] - self.nnei_r = np.cumsum(self.sel_r)[-1] - self.nnei = self.nnei_a + self.nnei_r - self.ndescrpt_a = self.nnei_a * 4 - self.ndescrpt_r = self.nnei_r * 1 - self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r - # network size - self.n_neuron = j_must_have_d (jdata, 'fitting_neuron', ['n_neuron']) - self.resnet_dt = True - if j_have(jdata, 'resnet_dt') : - warnings.warn("the key \"%s\" is deprecated, please use \"%s\" instead" % ('resnet_dt','fitting_resnet_dt')) - self.resnet_dt = jdata['resnet_dt'] - if j_have(jdata, 'fitting_resnet_dt') : - self.resnet_dt = jdata['fitting_resnet_dt'] - if self.use_smooth : - if j_have(jdata, 'type_fitting_net') : - self.type_fitting_net = jdata['type_fitting_net'] - else : - self.type_fitting_net = False - - self.numb_test = j_must_have (jdata, 'numb_test') - self.useBN = False - - if 'use_srtab' in jdata : - self.srtab = TabInter(jdata['use_srtab']) - self.smin_alpha = j_must_have(jdata, 'smin_alpha') - self.sw_rmin = j_must_have(jdata, 'sw_rmin') - self.sw_rmax = j_must_have(jdata, 'sw_rmax') - else : - self.srtab = None - - self.start_pref_e = j_must_have (jdata, 'start_pref_e') - self.limit_pref_e = j_must_have (jdata, 'limit_pref_e') - self.start_pref_f = j_must_have (jdata, 'start_pref_f') - self.limit_pref_f = j_must_have (jdata, 'limit_pref_f') - self.start_pref_v = j_must_have (jdata, 'start_pref_v') - self.limit_pref_v = j_must_have (jdata, 'limit_pref_v') - self.start_pref_ae = 0 - if j_have(jdata, 'start_pref_ae') : - self.start_pref_ae = jdata['start_pref_ae'] - self.limit_pref_ae = 0 - if j_have(jdata, 'limit_pref_ae') : - self.limit_pref_ae = jdata['limit_pref_ae'] - self.has_e = (self.start_pref_e != 0 or self.limit_pref_e != 0) - self.has_f = (self.start_pref_f != 0 or self.limit_pref_f != 0) - self.has_v = (self.start_pref_v != 0 or self.limit_pref_v != 0) - self.has_ae = (self.start_pref_ae != 0 or self.limit_pref_ae != 0) - - self.disp_file = "lcurve.out" - if j_have (jdata, "disp_file") : self.disp_file = jdata["disp_file"] - self.disp_freq = j_must_have (jdata, 'disp_freq') - self.save_freq = j_must_have (jdata, 'save_freq') - self.save_ckpt = j_must_have (jdata, 'save_ckpt') - - self.seed = None - if j_have (jdata, 'seed') : - self.seed = jdata['seed'] - - self.display_in_training = j_must_have (jdata, 'disp_training') - self.timing_in_training = j_must_have (jdata, 'time_training') - self.profiling = False - if j_have (jdata, 'profiling') : - self.profiling = jdata['profiling'] - if self.profiling : - self.profiling_file = j_must_have (jdata, 'profiling_file') - - self.sys_weights = None - if j_have(jdata, 'sys_weights') : - self.sys_weights = jdata['sys_weights'] - - - def _message (self, msg) : - self.run_opt.message(msg) - - def build (self, - data, - lr) : - self.lr = lr - self.ntypes = len(self.sel_a) - assert (self.ntypes == len(self.sel_r)), "size sel r array should match ntypes" - assert (self.ntypes == data.get_ntypes()), "ntypes should match that found in data" - - self.batch_size = data.get_batch_size() - - self.numb_fparam = data.numb_fparam() - if self.numb_fparam > 0 : - self._message("training with %d frame parameter(s)" % self.numb_fparam) - elif self.numb_fparam < 0 : - self._message("training without frame parameter") - else : - raise RuntimeError("number of frame parameter == 0") - - self.type_map = data.get_type_map() - - davg, dstd, bias_e = self._data_stat(data) - - worker_device = "/job:%s/task:%d/%s" % (self.run_opt.my_job_name, - self.run_opt.my_task_index, - self.run_opt.my_device) - with tf.device(tf.train.replica_device_setter(worker_device = worker_device, - cluster = self.run_opt.cluster_spec)): - self._build_lr(lr) - self._build_network(davg, dstd, bias_e) - self._build_training() - - def _data_stat(self, data): +class Model() : + def data_stat(self, data): all_stat_coord = [] all_stat_box = [] all_stat_type = [] @@ -231,823 +26,29 @@ def _data_stat(self, data): all_natoms_vec.append(natoms_vec) all_default_mesh.append(default_mesh) - if self.use_smooth and not self.coord_norm : + if not self.coord_norm : davg, dstd = self.no_norm_dstats () - self._message("skipped coord/descrpt stats") else : davg, dstd = self.compute_dstats (all_stat_coord, all_stat_box, all_stat_type, all_natoms_vec, all_default_mesh) - self._message("computed coord/descrpt stats") - if self.run_opt.is_chief: - np.savetxt ("stat.avg.out", davg.T) - np.savetxt ("stat.std.out", dstd.T) + # if self.run_opt.is_chief: + # np.savetxt ("stat.avg.out", davg.T) + # np.savetxt ("stat.std.out", dstd.T) bias_atom_e = data.compute_energy_shift() - self._message("computed energy bias") + # self._message("computed energy bias") return davg, dstd, bias_atom_e - - def _build_lr(self, lr): - self._extra_train_ops = [] - self.global_step = tf.train.get_or_create_global_step() - self.starter_learning_rate = lr.start_lr() - self.learning_rate = tf.train.exponential_decay(lr.start_lr(), - self.global_step, - lr.decay_steps(), - lr.decay_rate(), - staircase=True) - self._message("built lr") - - def _build_network(self, davg, dstd, bias_atom_e): - - self.t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') - self.t_energy = tf.placeholder(global_ener_float_precision, [None], name='t_energy') - self.t_force = tf.placeholder(global_tf_float_precision, [None], name='t_force') - self.t_virial = tf.placeholder(global_tf_float_precision, [None], name='t_virial') - self.t_atom_ener = tf.placeholder(global_tf_float_precision, [None], name='t_atom_ener') - self.t_coord = tf.placeholder(global_tf_float_precision, [None], name='i_coord') - self.t_type = tf.placeholder(tf.int32, [None], name='i_type') - self.t_natoms = tf.placeholder(tf.int32, [self.ntypes+2], name='i_natoms') - self.t_box = tf.placeholder(global_tf_float_precision, [None, 9], name='i_box') - self.t_mesh = tf.placeholder(tf.int32, [None], name='i_mesh') - self.is_training = tf.placeholder(tf.bool) - if self.numb_fparam > 0 : - self.t_fparam = tf.placeholder(global_tf_float_precision, [None], name='i_fparam') - else : - self.t_fparam = None - - self.energy, self.force, self.virial, self.atom_ener \ - = self.build_interaction (self.t_coord, - self.t_type, - self.t_natoms, - self.t_box, - self.t_mesh, - self.t_fparam, - davg = davg, - dstd = dstd, - bias_atom_e = bias_atom_e, - suffix = "", - reuse = False) - - self.l2_l, self.l2_el, self.l2_fl, self.l2_vl, self.l2_ael \ - = self.loss (self.t_natoms, \ - self.t_prop_c, \ - self.t_energy, self.energy, \ - self.t_force, self.force, \ - self.t_virial, self.virial, \ - self.t_atom_ener, self.atom_ener, \ - suffix = "test") - - self._message("built network") - - def _build_training(self): - trainable_variables = tf.trainable_variables() - optimizer = tf.train.AdamOptimizer(learning_rate = self.learning_rate) - if self.run_opt.is_distrib : - optimizer = tf.train.SyncReplicasOptimizer( - optimizer, - replicas_to_aggregate = self.run_opt.cluster_spec.num_tasks("worker"), - total_num_replicas = self.run_opt.cluster_spec.num_tasks("worker"), - name = "sync_replicas") - self.sync_replicas_hook = optimizer.make_session_run_hook(self.run_opt.is_chief) - grads = tf.gradients(self.l2_l, trainable_variables) - apply_op = optimizer.apply_gradients (zip (grads, trainable_variables), - global_step=self.global_step, - name='train_step') - train_ops = [apply_op] + self._extra_train_ops - self.train_op = tf.group(*train_ops) - self._message("built training") - - def _init_sess_serial(self) : - self.sess = tf.Session( - config=tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, - inter_op_parallelism_threads=self.run_opt.num_inter_threads - )) - self.saver = tf.train.Saver() - saver = self.saver - if self.run_opt.init_mode == 'init_from_scratch' : - self._message("initialize model from scratch") - init_op = tf.global_variables_initializer() - self.sess.run(init_op) - fp = open(self.disp_file, "w") - fp.close () - elif self.run_opt.init_mode == 'init_from_model' : - self._message("initialize from model %s" % self.run_opt.init_model) - init_op = tf.global_variables_initializer() - self.sess.run(init_op) - saver.restore (self.sess, self.run_opt.init_model) - self.sess.run(self.global_step.assign(0)) - fp = open(self.disp_file, "w") - fp.close () - elif self.run_opt.init_mode == 'restart' : - self._message("restart from model %s" % self.run_opt.restart) - init_op = tf.global_variables_initializer() - self.sess.run(init_op) - saver.restore (self.sess, self.run_opt.restart) - else : - raise RuntimeError ("unkown init mode") - - def _init_sess_distrib(self): - ckpt_dir = os.path.join(os.getcwd(), self.save_ckpt) - assert(_is_subdir(ckpt_dir, os.getcwd())), "the checkpoint dir must be a subdir of the current dir" - if self.run_opt.init_mode == 'init_from_scratch' : - self._message("initialize model from scratch") - if self.run_opt.is_chief : - if os.path.exists(ckpt_dir): - shutil.rmtree(ckpt_dir) - if not os.path.exists(ckpt_dir) : - os.makedirs(ckpt_dir) - fp = open(self.disp_file, "w") - fp.close () - elif self.run_opt.init_mode == 'init_from_model' : - raise RuntimeError("distributed training does not support %s" % self.run_opt.init_mode) - elif self.run_opt.init_mode == 'restart' : - self._message("restart from model %s" % ckpt_dir) - if self.run_opt.is_chief : - assert(os.path.isdir(ckpt_dir)), "the checkpoint dir %s should exists" % ckpt_dir - else : - raise RuntimeError ("unkown init mode") - - saver = tf.train.Saver(max_to_keep = 1) - self.saver = None - # gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.5) - # config = tf.ConfigProto(allow_soft_placement=True, - # gpu_options = gpu_options, - # intra_op_parallelism_threads=self.run_opt.num_intra_threads, - # inter_op_parallelism_threads=self.run_opt.num_inter_threads) - config = tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, - inter_op_parallelism_threads=self.run_opt.num_inter_threads) - # The stop_hook handles stopping after running given steps - # stop_hook = tf.train.StopAtStepHook(last_step = stop_batch) - # hooks = [self.sync_replicas_hook, stop_hook] - hooks = [self.sync_replicas_hook] - scaffold = tf.train.Scaffold(saver=saver) - # Use monitor session for distributed computation - self.sess = tf.train.MonitoredTrainingSession(master = self.run_opt.server.target, - is_chief = self.run_opt.is_chief, - config = config, - hooks = hooks, - scaffold = scaffold, - checkpoint_dir = ckpt_dir) - # , - # save_checkpoint_steps = self.save_freq) - - def train (self, - data, - stop_batch) : - if self.run_opt.is_distrib : - self._init_sess_distrib() - else : - self._init_sess_serial() - - self.print_head() - fp = None - if self.run_opt.is_chief : - fp = open(self.disp_file, "a") - - cur_batch = self.sess.run(self.global_step) - self.cur_batch = cur_batch - self.run_opt.message("start training at lr %.2e (== %.2e), final lr will be %.2e" % - (self.sess.run(self.learning_rate), - self.lr.value(cur_batch), - self.lr.value(stop_batch)) - ) - - prf_options = None - prf_run_metadata = None - if self.profiling : - prf_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE) - prf_run_metadata = tf.RunMetadata() - - train_time = 0 - while cur_batch < stop_batch : - batch_prop_c, \ - batch_energy, batch_force, batch_virial, batch_atom_ener, \ - batch_coord, batch_box, batch_type, batch_fparam, \ - natoms_vec, \ - default_mesh \ - = data.get_batch (sys_weights = self.sys_weights) - cur_batch_size = batch_energy.shape[0] - feed_dict_batch = {self.t_prop_c: batch_prop_c, - self.t_energy: batch_energy, - self.t_force: np.reshape(batch_force, [-1]), - self.t_virial: np.reshape(batch_virial, [-1]), - self.t_atom_ener: np.reshape(batch_atom_ener, [-1]), - self.t_coord: np.reshape(batch_coord, [-1]), - self.t_box: batch_box, - self.t_type: np.reshape(batch_type, [-1]), - self.t_natoms: natoms_vec, - self.t_mesh: default_mesh, - self.is_training: True} - if self.numb_fparam > 0 : - feed_dict_batch[self.t_fparam] = np.reshape(batch_fparam, [-1]) - if self.display_in_training and cur_batch == 0 : - self.test_on_the_fly(fp, data, feed_dict_batch) - if self.timing_in_training : tic = time.time() - self.sess.run([self.train_op], feed_dict = feed_dict_batch, options=prf_options, run_metadata=prf_run_metadata) - if self.timing_in_training : toc = time.time() - if self.timing_in_training : train_time += toc - tic - cur_batch = self.sess.run(self.global_step) - self.cur_batch = cur_batch - - if self.display_in_training and (cur_batch % self.disp_freq == 0) : - tic = time.time() - self.test_on_the_fly(fp, data, feed_dict_batch) - toc = time.time() - test_time = toc - tic - if self.timing_in_training : - self._message("batch %7d training time %.2f s, testing time %.2f s" - % (cur_batch, train_time, test_time)) - train_time = 0 - if self.save_freq > 0 and cur_batch % self.save_freq == 0 and self.run_opt.is_chief : - if self.saver is not None : - self.saver.save (self.sess, os.getcwd() + "/" + self.save_ckpt) - self._message("saved checkpoint %s" % self.save_ckpt) - if self.run_opt.is_chief: - fp.close () - if self.profiling and self.run_opt.is_chief : - fetched_timeline = timeline.Timeline(prf_run_metadata.step_stats) - chrome_trace = fetched_timeline.generate_chrome_trace_format() - with open(self.profiling_file, 'w') as f: - f.write(chrome_trace) - - def get_global_step (self) : - return self.sess.run(self.global_step) - - def print_head (self) : - if self.run_opt.is_chief: - fp = open(self.disp_file, "a") - print_str = "# %5s" % 'batch' - prop_fmt = ' %9s %9s' - print_str += prop_fmt % ('l2_tst', 'l2_trn') - if self.has_e : - print_str += prop_fmt % ('l2_e_tst', 'l2_e_trn') - if self.has_ae : - print_str += prop_fmt % ('l2_ae_tst', 'l2_ae_trn') - if self.has_f : - print_str += prop_fmt % ('l2_f_tst', 'l2_f_trn') - if self.has_v : - print_str += prop_fmt % ('l2_v_tst', 'l2_v_trn') - print_str += ' %8s\n' % 'lr' - fp.write(print_str) - fp.close () - - def test_on_the_fly (self, - fp, - data, - feed_dict_batch) : - test_prop_c, \ - test_energy, test_force, test_virial, test_atom_ener, \ - test_coord, test_box, test_type, test_fparam, \ - natoms_vec, \ - default_mesh \ - = data.get_test () - feed_dict_test = {self.t_prop_c: test_prop_c, - self.t_energy: test_energy [:self.numb_test], - self.t_force: np.reshape(test_force [:self.numb_test, :], [-1]), - self.t_virial: np.reshape(test_virial [:self.numb_test, :], [-1]), - self.t_atom_ener: np.reshape(test_atom_ener[:self.numb_test, :], [-1]), - self.t_coord: np.reshape(test_coord [:self.numb_test, :], [-1]), - self.t_box: test_box [:self.numb_test, :], - self.t_type: np.reshape(test_type [:self.numb_test, :], [-1]), - self.t_natoms: natoms_vec, - self.t_mesh: default_mesh, - self.is_training: False} - if self.numb_fparam > 0 : - feed_dict_test[self.t_fparam] = np.reshape(test_fparam [:self.numb_test, :], [-1]) - error_test, error_e_test, error_f_test, error_v_test, error_ae_test \ - = self.sess.run([self.l2_l, \ - self.l2_el, \ - self.l2_fl, \ - self.l2_vl, \ - self.l2_ael], - feed_dict=feed_dict_test) - error_train, error_e_train, error_f_train, error_v_train, error_ae_train \ - = self.sess.run([self.l2_l, \ - self.l2_el, \ - self.l2_fl, \ - self.l2_vl, \ - self.l2_ael], - feed_dict=feed_dict_batch) - cur_batch = self.cur_batch - current_lr = self.sess.run(self.learning_rate) - if self.run_opt.is_chief: - print_str = "%7d" % cur_batch - prop_fmt = " %9.2e %9.2e" - print_str += prop_fmt % (np.sqrt(error_test), np.sqrt(error_train)) - if self.has_e : - print_str += prop_fmt % (np.sqrt(error_e_test) / natoms_vec[0], np.sqrt(error_e_train) / natoms_vec[0]) - if self.has_ae : - print_str += prop_fmt % (np.sqrt(error_ae_test), np.sqrt(error_ae_train)) - if self.has_f : - print_str += prop_fmt % (np.sqrt(error_f_test), np.sqrt(error_f_train)) - if self.has_v : - print_str += prop_fmt % (np.sqrt(error_v_test) / natoms_vec[0], np.sqrt(error_v_train) / natoms_vec[0]) - print_str += " %8.1e\n" % current_lr - fp.write(print_str) - fp.flush () - - def compute_dstats_sys_smth (self, - data_coord, - data_box, - data_atype, - natoms_vec, - mesh, - reuse = None) : - avg_zero = np.zeros([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) - std_ones = np.ones ([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) - sub_graph = tf.Graph() - with sub_graph.as_default(): - descrpt, descrpt_deriv, rij, nlist \ - = op_module.descrpt_norot (tf.constant(data_coord), - tf.constant(data_atype), - tf.constant(natoms_vec, dtype = tf.int32), - tf.constant(data_box), - tf.constant(mesh), - tf.constant(avg_zero), - tf.constant(std_ones), - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - rcut_r_smth = self.rcut_r_smth, - sel_a = self.sel_a, - sel_r = self.sel_r) - # self.sess.run(tf.global_variables_initializer()) - sub_sess = tf.Session(graph = sub_graph, - config=tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, - inter_op_parallelism_threads=self.run_opt.num_inter_threads - - )) - dd_all = sub_sess.run(descrpt) - sub_sess.close() - natoms = natoms_vec - dd_all = np.reshape(dd_all, [-1, self.ndescrpt * natoms[0]]) - start_index = 0 - sysr = [] - sysa = [] - sysn = [] - sysr2 = [] - sysa2 = [] - for type_i in range(self.ntypes): - end_index = start_index + self.ndescrpt * natoms[2+type_i] - dd = dd_all[:, start_index:end_index] - dd = np.reshape(dd, [-1, self.ndescrpt]) - start_index = end_index - # compute - dd = np.reshape (dd, [-1, 4]) - ddr = dd[:,:1] - dda = dd[:,1:] - sumr = np.sum(ddr) - suma = np.sum(dda) / 3. - sumn = dd.shape[0] - sumr2 = np.sum(np.multiply(ddr, ddr)) - suma2 = np.sum(np.multiply(dda, dda)) / 3. - sysr.append(sumr) - sysa.append(suma) - sysn.append(sumn) - sysr2.append(sumr2) - sysa2.append(suma2) - return sysr, sysr2, sysa, sysa2, sysn - - def compute_dstats_sys_nonsmth (self, - data_coord, - data_box, - data_atype, - natoms_vec, - mesh, - reuse = None) : - avg_zero = np.zeros([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) - std_ones = np.ones ([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) - sub_graph = tf.Graph() - with sub_graph.as_default(): - descrpt, descrpt_deriv, rij, nlist, axis \ - = op_module.descrpt (tf.constant(data_coord), - tf.constant(data_atype), - tf.constant(natoms_vec, dtype = tf.int32), - tf.constant(data_box), - tf.constant(mesh), - tf.constant(avg_zero), - tf.constant(std_ones), - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - sel_a = self.sel_a, - sel_r = self.sel_r, - axis_rule = self.axis_rule) - # self.sess.run(tf.global_variables_initializer()) - sub_sess = tf.Session(graph = sub_graph, - config=tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, - inter_op_parallelism_threads=self.run_opt.num_inter_threads - )) - dd_all = sub_sess.run(descrpt) - sub_sess.close() - natoms = natoms_vec - dd_all = np.reshape(dd_all, [-1, self.ndescrpt * natoms[0]]) - start_index = 0 - sysv = [] - sysn = [] - sysv2 = [] - for type_i in range(self.ntypes): - end_index = start_index + self.ndescrpt * natoms[2+type_i] - dd = dd_all[:, start_index:end_index] - dd = np.reshape(dd, [-1, self.ndescrpt]) - start_index = end_index - # compute - sumv = np.sum(dd, axis = 0) - sumn = dd.shape[0] - sumv2 = np.sum(np.multiply(dd,dd), axis = 0) - sysv.append(sumv) - sysn.append(sumn) - sysv2.append(sumv2) - return sysv, sysv2, sysn - - def compute_std (self,sumv2, sumv, sumn) : - return np.sqrt(sumv2/sumn - np.multiply(sumv/sumn, sumv/sumn)) - - def compute_dstats (self, - data_coord, - data_box, - data_atype, - natoms_vec, - mesh, - reuse = None) : - env_bk = None - if 'TF_CPP_MIN_LOG_LEVEL' in os.environ: - env_bk = os.environ['TF_CPP_MIN_LOG_LEVEL'] - os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' - - all_davg = [] - all_dstd = [] - if self.use_smooth: - sumr = [] - suma = [] - sumn = [] - sumr2 = [] - suma2 = [] - for cc,bb,tt,nn,mm in zip(data_coord,data_box,data_atype,natoms_vec,mesh) : - sysr,sysr2,sysa,sysa2,sysn \ - = self.compute_dstats_sys_smth(cc,bb,tt,nn,mm,reuse) - sumr.append(sysr) - suma.append(sysa) - sumn.append(sysn) - sumr2.append(sysr2) - suma2.append(sysa2) - sumr = np.sum(sumr, axis = 0) - suma = np.sum(suma, axis = 0) - sumn = np.sum(sumn, axis = 0) - sumr2 = np.sum(sumr2, axis = 0) - suma2 = np.sum(suma2, axis = 0) - for type_i in range(self.ntypes) : - davgunit = [sumr[type_i]/sumn[type_i], 0, 0, 0] - dstdunit = [self.compute_std(sumr2[type_i], sumr[type_i], sumn[type_i]), - self.compute_std(suma2[type_i], suma[type_i], sumn[type_i]), - self.compute_std(suma2[type_i], suma[type_i], sumn[type_i]), - self.compute_std(suma2[type_i], suma[type_i], sumn[type_i]) - ] - davg = np.tile(davgunit, self.ndescrpt // 4) - dstd = np.tile(dstdunit, self.ndescrpt // 4) - all_davg.append(davg) - all_dstd.append(dstd) - else : - sumv = [] - sumn = [] - sumv2 = [] - for cc,bb,tt,nn,mm in zip(data_coord,data_box,data_atype,natoms_vec,mesh) : - sysv,sysv2,sysn \ - = self.compute_dstats_sys_nonsmth(cc,bb,tt,nn,mm,reuse) - sumv.append(sysv) - sumn.append(sysn) - sumv2.append(sysv2) - sumv = np.sum(sumv, axis = 0) - sumn = np.sum(sumn, axis = 0) - sumv2 = np.sum(sumv2, axis = 0) - for type_i in range(self.ntypes) : - davg = sumv[type_i] / sumn[type_i] - dstd = self.compute_std(sumv2[type_i], sumv[type_i], sumn[type_i]) - for ii in range (len(dstd)) : - if (np.abs(dstd[ii]) < 1e-2) : - dstd[ii] = 1e-2 - all_davg.append(davg) - all_dstd.append(dstd) - - davg = np.array(all_davg) - dstd = np.array(all_dstd) - if env_bk is not None : - os.environ['TF_CPP_MIN_LOG_LEVEL'] = env_bk - else : - os.environ.pop('TF_CPP_MIN_LOG_LEVEL', None) - return davg, dstd - - def no_norm_dstats (self, avgv = 0, stdv = 1) : - davg = np.zeros([self.ntypes, self.ndescrpt]) + avgv - dstd = np.ones ([self.ntypes, self.ndescrpt]) * stdv - return davg, dstd - - def loss (self, - natoms, - prop_c, - energy, - energy_hat, - force, - force_hat, - virial, - virial_hat, - atom_ener, - atom_ener_hat, - suffix): - l2_ener_loss = tf.reduce_mean( tf.square(energy - energy_hat), name='l2_'+suffix) - - force_reshape = tf.reshape (force, [-1]) - force_hat_reshape = tf.reshape (force_hat, [-1]) - l2_force_loss = tf.reduce_mean (tf.square(force_hat_reshape - force_reshape), name = "l2_force_" + suffix) - - virial_reshape = tf.reshape (virial, [-1]) - virial_hat_reshape = tf.reshape (virial_hat, [-1]) - l2_virial_loss = tf.reduce_mean (tf.square(virial_hat_reshape - virial_reshape), name = "l2_virial_" + suffix) - - atom_ener_reshape = tf.reshape (atom_ener, [-1]) - atom_ener_hat_reshape = tf.reshape (atom_ener_hat, [-1]) - l2_atom_ener_loss = tf.reduce_mean (tf.square(atom_ener_hat_reshape - atom_ener_reshape), name = "l2_atom_ener_" + suffix) - - atom_norm = 1./ global_cvt_2_tf_float(natoms[0]) - atom_norm_ener = 1./ global_cvt_2_ener_float(natoms[0]) - pref_e = global_cvt_2_ener_float(prop_c[0] * (self.limit_pref_e + (self.start_pref_e - self.limit_pref_e) * self.learning_rate / self.starter_learning_rate) ) - pref_f = global_cvt_2_tf_float(prop_c[1] * (self.limit_pref_f + (self.start_pref_f - self.limit_pref_f) * self.learning_rate / self.starter_learning_rate) ) - pref_v = global_cvt_2_tf_float(prop_c[2] * (self.limit_pref_v + (self.start_pref_v - self.limit_pref_v) * self.learning_rate / self.starter_learning_rate) ) - pref_ae= global_cvt_2_tf_float(prop_c[3] * (self.limit_pref_ae+ (self.start_pref_ae-self.limit_pref_ae) * self.learning_rate / self.starter_learning_rate) ) - - l2_loss = 0 - if self.has_e : - l2_loss += atom_norm_ener * (pref_e * l2_ener_loss) - if self.has_f : - l2_loss += global_cvt_2_ener_float(pref_f * l2_force_loss) - if self.has_v : - l2_loss += global_cvt_2_ener_float(atom_norm * (pref_v * l2_virial_loss)) - if self.has_ae : - l2_loss += global_cvt_2_ener_float(pref_ae * l2_atom_ener_loss) - - return l2_loss, l2_ener_loss, l2_force_loss, l2_virial_loss, l2_atom_ener_loss - - def build_interaction (self, - coord_, - atype_, - natoms, - box, - mesh, - fparam, - davg = None, - dstd = None, - bias_atom_e = None, - suffix = '', - reuse = None): - with tf.variable_scope('model_attr' + suffix, reuse = reuse) : - if davg is None: - davg = np.zeros([self.ntypes, self.ndescrpt]) - if dstd is None: - dstd = np.ones ([self.ntypes, self.ndescrpt]) - t_rcut = tf.constant(np.max([self.rcut_r, self.rcut_a]), - name = 'rcut', - dtype = global_tf_float_precision) - t_ntypes = tf.constant(self.ntypes, - name = 'ntypes', - dtype = tf.int32) - t_dfparam = tf.constant(self.numb_fparam, - name = 'dfparam', - dtype = tf.int32) - t_tmap = tf.constant(' '.join(self.type_map), - name = 'tmap', - dtype = tf.string) - self.t_avg = tf.get_variable('t_avg', - davg.shape, - dtype = global_tf_float_precision, - trainable = False, - initializer = tf.constant_initializer(davg, dtype = global_tf_float_precision)) - self.t_std = tf.get_variable('t_std', - dstd.shape, - dtype = global_tf_float_precision, - trainable = False, - initializer = tf.constant_initializer(dstd, dtype = global_tf_float_precision)) - if self.srtab is not None : - tab_info, tab_data = self.srtab.get() - self.tab_info = tf.get_variable('t_tab_info', - tab_info.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) - self.tab_data = tf.get_variable('t_tab_data', - tab_data.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) - - coord = tf.reshape (coord_, [-1, natoms[1] * 3]) - atype = tf.reshape (atype_, [-1, natoms[1]]) - - if self.use_smooth : - descrpt, descrpt_deriv, rij, nlist \ - = op_module.descrpt_norot (coord, - atype, - natoms, - box, - mesh, - self.t_avg, - self.t_std, - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - rcut_r_smth = self.rcut_r_smth, - sel_a = self.sel_a, - sel_r = self.sel_r) - else : - descrpt, descrpt_deriv, rij, nlist, axis \ - = op_module.descrpt (coord, - atype, - natoms, - box, - mesh, - self.t_avg, - self.t_std, - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - sel_a = self.sel_a, - sel_r = self.sel_r, - axis_rule = self.axis_rule) - - descrpt_reshape = tf.reshape(descrpt, [-1, self.ndescrpt]) - - atom_ener = self.build_atom_net (descrpt_reshape, fparam, natoms, bias_atom_e = bias_atom_e, reuse = reuse) - - if self.srtab is not None : - sw_lambda, sw_deriv \ - = op_module.soft_min_switch(atype, - rij, - nlist, - natoms, - sel_a = self.sel_a, - sel_r = self.sel_r, - alpha = self.smin_alpha, - rmin = self.sw_rmin, - rmax = self.sw_rmax) - inv_sw_lambda = 1.0 - sw_lambda - # NOTICE: - # atom energy is not scaled, - # force and virial are scaled - tab_atom_ener, tab_force, tab_atom_virial \ - = op_module.tab_inter(self.tab_info, - self.tab_data, - atype, - rij, - nlist, - natoms, - sw_lambda, - sel_a = self.sel_a, - sel_r = self.sel_r) - energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, natoms[0]]) - tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) - atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener - energy_raw = tab_atom_ener + atom_ener - else : - energy_raw = atom_ener - - energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'o_atom_energy'+suffix) - energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='o_energy'+suffix) - - net_deriv_tmp = tf.gradients (atom_ener, descrpt_reshape) - net_deriv = net_deriv_tmp[0] - net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) - - if self.use_smooth : - force = op_module.prod_force_norot (net_deriv_reshape, - descrpt_deriv, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - else : - force = op_module.prod_force (net_deriv_reshape, - descrpt_deriv, - nlist, - axis, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - if self.srtab is not None : - sw_force \ - = op_module.soft_min_force(energy_diff, - sw_deriv, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - force = force + sw_force + tab_force - - force = tf.reshape (force, [-1, 3 * natoms[1]], name = "o_force"+suffix) - - if self.use_smooth : - virial, atom_virial \ - = op_module.prod_virial_norot (net_deriv_reshape, - descrpt_deriv, - rij, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - else : - virial, atom_virial \ - = op_module.prod_virial (net_deriv_reshape, - descrpt_deriv, - rij, - nlist, - axis, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - if self.srtab is not None : - sw_virial, sw_atom_virial \ - = op_module.soft_min_virial (energy_diff, - sw_deriv, - rij, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - atom_virial = atom_virial + sw_atom_virial + tab_atom_virial - virial = virial + sw_virial \ - + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, natoms[1], 9]), axis = 1) - - virial = tf.reshape (virial, [-1, 9], name = "o_virial"+suffix) - atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "o_atom_virial"+suffix) - - return energy, force, virial, energy_raw - def build_atom_net (self, - inputs, - fparam, - natoms, - bias_atom_e = None, - reuse = None) : - start_index = 0 - inputs = tf.reshape(inputs, [-1, self.ndescrpt * natoms[0]]) - shape = inputs.get_shape().as_list() - if bias_atom_e is not None : - assert(len(bias_atom_e) == self.ntypes) - - for type_i in range(self.ntypes): - # cut-out inputs - inputs_i = tf.slice (inputs, - [ 0, start_index* self.ndescrpt], - [-1, natoms[2+type_i]* self.ndescrpt] ) - inputs_i = tf.reshape(inputs_i, [-1, self.ndescrpt]) - start_index += natoms[2+type_i] - if bias_atom_e is None : - type_bias_ae = 0.0 - else : - type_bias_ae = bias_atom_e[type_i] - - # compute atom energy - if self.use_smooth : - if self.type_fitting_net : - layer = self._DS_layer_type_ext(inputs_i, name='DS_layer_type_'+str(type_i), natoms=natoms, reuse=reuse, seed = self.seed) - else : - layer = self._DS_layer(inputs_i, name='DS_layer_type_'+str(type_i), natoms=natoms, reuse=reuse, seed = self.seed) - if self.numb_fparam > 0 : - ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) - ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) - ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) - layer = tf.concat([layer, ext_fparam], axis = 1) - for ii in range(0,len(self.n_neuron)) : - if ii >= 1 and self.n_neuron[ii] == self.n_neuron[ii-1] : - layer+= self._one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i), reuse=reuse, seed = self.seed, use_timestep = self.resnet_dt) - else : - layer = self._one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i), reuse=reuse, seed = self.seed) - else : - if self.numb_fparam > 0 : - ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) - ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) - ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) - layer = tf.concat([layer, ext_fparam], axis = 1) - layer = self._one_layer(inputs_i, self.n_neuron[0], name='layer_0_type_'+str(type_i), reuse=reuse, seed = self.seed) - for ii in range(1,len(self.n_neuron)) : - layer = self._one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i), reuse=reuse, seed = self.seed) - final_layer = self._one_layer(layer, 1, activation_fn = None, bavg = type_bias_ae, name='final_layer_type_'+str(type_i), reuse=reuse, seed = self.seed) - final_layer = tf.reshape(final_layer, [-1, natoms[2+type_i]]) - # final_layer = tf.cond (tf.equal(natoms[2+type_i], 0), lambda: tf.zeros((0, 0), dtype=global_tf_float_precision), lambda : tf.reshape(final_layer, [-1, natoms[2+type_i]])) - - # concat the results - if type_i == 0: - outs = final_layer - else: - outs = tf.concat([outs, final_layer], axis = 1) - - - return tf.reshape(outs, [-1]) - - def _one_layer(self, - inputs, - outputs_size, - activation_fn=tf.nn.tanh, - stddev=1.0, - bavg=0.0, - name='linear', - reuse=None, - seed=None, - use_timestep = False): + def one_layer(self, + inputs, + outputs_size, + activation_fn=tf.nn.tanh, + stddev=1.0, + bavg=0.0, + name='linear', + reuse=None, + seed=None, + use_timestep = False): with tf.variable_scope(name, reuse=reuse): shape = inputs.get_shape().as_list() w = tf.get_variable('matrix', @@ -1082,162 +83,3 @@ def _one_layer(self, else: return hidden - def _DS_layer(self, - inputs, - natoms, - activation_fn=tf.nn.tanh, - stddev=1.0, - bavg=0.0, - name='linear', - reuse=None, - seed=None): - # natom x (nei x 4) - shape = inputs.get_shape().as_list() - outputs_size = [1] + self.filter_neuron - outputs_size_2 = self.n_axis_neuron - with tf.variable_scope(name, reuse=reuse): - start_index = 0 - xyz_scatter_total = [] - for type_i in range(self.ntypes): - # cut-out inputs - # with natom x (nei_type_i x 4) - inputs_i = tf.slice (inputs, - [ 0, start_index* 4], - [-1, self.sel_a[type_i]* 4] ) - start_index += self.sel_a[type_i] - shape_i = inputs_i.get_shape().as_list() - # with (natom x nei_type_i) x 4 - inputs_reshape = tf.reshape(inputs_i, [-1, 4]) - xyz_scatter = tf.reshape(tf.slice(inputs_reshape, [0,0],[-1,1]),[-1,1]) - for ii in range(1, len(outputs_size)): - w = tf.get_variable('matrix_'+str(ii)+'_'+str(type_i), - [outputs_size[ii - 1], outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=stddev/np.sqrt(outputs_size[ii]+outputs_size[ii-1]), seed = seed)) - b = tf.get_variable('bias_'+str(ii)+'_'+str(type_i), - [1, outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=stddev, mean = bavg, seed = seed)) - if self.filter_resnet_dt : - idt = tf.get_variable('idt_'+str(ii)+'_'+str(type_i), - [1, outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=0.001, mean = 1.0, seed = seed)) - if outputs_size[ii] == outputs_size[ii-1]: - if self.filter_resnet_dt : - xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) * idt - else : - xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) - elif outputs_size[ii] == outputs_size[ii-1] * 2: - if self.filter_resnet_dt : - xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) * idt - else : - xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) - else: - xyz_scatter = activation_fn(tf.matmul(xyz_scatter, w) + b) - # natom x nei_type_i x out_size - xyz_scatter = tf.reshape(xyz_scatter, (-1, shape_i[1]//4, outputs_size[-1])) - xyz_scatter_total.append(xyz_scatter) - - # natom x nei x outputs_size - xyz_scatter = tf.concat(xyz_scatter_total, axis=1) - # natom x nei x 4 - inputs_reshape = tf.reshape(inputs, [-1, shape[1]//4, 4]) - # natom x 4 x outputs_size - xyz_scatter_1 = tf.matmul(inputs_reshape, xyz_scatter, transpose_a = True) - xyz_scatter_1 = xyz_scatter_1 * (4.0 / shape[1]) - # natom x 4 x outputs_size_2 - xyz_scatter_2 = tf.slice(xyz_scatter_1, [0,0,0],[-1,-1,outputs_size_2]) - # natom x outputs_size x outputs_size_2 - result = tf.matmul(xyz_scatter_1, xyz_scatter_2, transpose_a = True) - # natom x (outputs_size x outputs_size_2) - result = tf.reshape(result, [-1, outputs_size_2 * outputs_size[-1]]) - - return result - - def _DS_layer_type_ext(self, - inputs, - natoms, - activation_fn=tf.nn.tanh, - stddev=1.0, - bavg=0.0, - name='linear', - reuse=None, - seed=None): - # natom x (nei x 4) - shape = inputs.get_shape().as_list() - outputs_size = [1] + self.filter_neuron - outputs_size_2 = self.n_axis_neuron - with tf.variable_scope(name, reuse=reuse): - start_index = 0 - result_all = [] - xyz_scatter_1_all = [] - xyz_scatter_2_all = [] - for type_i in range(self.ntypes): - # cut-out inputs - # with natom x (nei_type_i x 4) - inputs_i = tf.slice (inputs, - [ 0, start_index* 4], - [-1, self.sel_a[type_i]* 4] ) - start_index += self.sel_a[type_i] - shape_i = inputs_i.get_shape().as_list() - # with (natom x nei_type_i) x 4 - inputs_reshape = tf.reshape(inputs_i, [-1, 4]) - xyz_scatter = tf.reshape(tf.slice(inputs_reshape, [0,0],[-1,1]),[-1,1]) - for ii in range(1, len(outputs_size)): - w = tf.get_variable('matrix_'+str(ii)+'_'+str(type_i), - [outputs_size[ii - 1], outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=stddev/np.sqrt(outputs_size[ii]+outputs_size[ii-1]), seed = seed)) - b = tf.get_variable('bias_'+str(ii)+'_'+str(type_i), - [1, outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=stddev, mean = bavg, seed = seed)) - if self.filter_resnet_dt : - idt = tf.get_variable('idt_'+str(ii)+'_'+str(type_i), - [1, outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=0.001, mean = 1.0, seed = seed)) - if outputs_size[ii] == outputs_size[ii-1]: - if self.filter_resnet_dt : - xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) * idt - else : - xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) - elif outputs_size[ii] == outputs_size[ii-1] * 2: - if self.filter_resnet_dt : - xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) * idt - else : - xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) - else: - xyz_scatter = activation_fn(tf.matmul(xyz_scatter, w) + b) - # natom x nei_type_i x out_size - xyz_scatter = tf.reshape(xyz_scatter, (-1, shape_i[1]//4, outputs_size[-1])) - # natom x nei_type_i x 4 - inputs_i_reshape = tf.reshape(inputs_i, [-1, shape_i[1]//4, 4]) - # natom x 4 x outputs_size - xyz_scatter_1 = tf.matmul(inputs_i_reshape, xyz_scatter, transpose_a = True) - xyz_scatter_1 = xyz_scatter_1 * (4.0 / shape_i[1]) - # natom x 4 x outputs_size_2 - xyz_scatter_2 = tf.slice(xyz_scatter_1, [0,0,0],[-1,-1,outputs_size_2]) - xyz_scatter_1_all.append(xyz_scatter_1) - xyz_scatter_2_all.append(xyz_scatter_2) - - # for type_i in range(self.ntypes): - # for type_j in range(type_i, self.ntypes): - # # natom x outputs_size x outputs_size_2 - # result = tf.matmul(xyz_scatter_1_all[type_i], xyz_scatter_2_all[type_j], transpose_a = True) - # # natom x (outputs_size x outputs_size_2) - # result = tf.reshape(result, [-1, outputs_size_2 * outputs_size[-1]]) - # result_all.append(tf.identity(result)) - xyz_scatter_2_coll = tf.concat(xyz_scatter_2_all, axis = 2) - for type_i in range(self.ntypes) : - # natom x outputs_size x (outputs_size_2 x ntypes) - result = tf.matmul(xyz_scatter_1_all[type_i], xyz_scatter_2_coll, transpose_a = True) - # natom x (outputs_size x outputs_size_2 x ntypes) - result = tf.reshape(result, [-1, outputs_size_2 * self.ntypes * outputs_size[-1]]) - result_all.append(tf.identity(result)) - - # natom x (ntypes x outputs_size x outputs_size_2 x ntypes) - result_all = tf.concat(result_all, axis = 1) - - return result_all diff --git a/source/train/Trainer.py b/source/train/Trainer.py new file mode 100644 index 0000000000..ec3bcba4cc --- /dev/null +++ b/source/train/Trainer.py @@ -0,0 +1,505 @@ +#!/usr/bin/env python3 +import os +import sys +import time +import shutil +import warnings +import numpy as np +import tensorflow as tf +from deepmd.RunOptions import global_tf_float_precision +from deepmd.RunOptions import global_np_float_precision +from deepmd.RunOptions import global_ener_float_precision +from deepmd.RunOptions import global_cvt_2_tf_float +from deepmd.RunOptions import global_cvt_2_ener_float +from ModelSeA import ModelSeA +from ModelLocFrame import ModelLocFrame + +from tensorflow.python.framework import ops +from tensorflow.python.client import timeline + +# load force module +module_path = os.path.dirname(os.path.realpath(__file__)) + "/" +assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" +op_module = tf.load_op_library(module_path + "libop_abi.so") + +# load grad of force module +sys.path.append (module_path ) +import deepmd._prod_force_grad +import deepmd._prod_virial_grad +import deepmd._prod_force_norot_grad +import deepmd._prod_virial_norot_grad +import deepmd._soft_min_force_grad +import deepmd._soft_min_virial_grad +from deepmd.RunOptions import RunOptions +from deepmd.TabInter import TabInter + +from deepmd.common import j_must_have, j_must_have_d, j_have + +def _is_subdir(path, directory): + path = os.path.realpath(path) + directory = os.path.realpath(directory) + if path == directory: + return False + relative = os.path.relpath(path, directory) + os.sep + return not relative.startswith(os.pardir + os.sep) + +class LearingRate (object) : + def __init__ (self, + jdata, + tot_numb_batches) : + self.decay_steps_ = j_must_have(jdata, 'decay_steps') + self.decay_rate_ = j_must_have(jdata, 'decay_rate') + self.start_lr_ = j_must_have(jdata, 'start_lr') + self.tot_numb_batches = tot_numb_batches + + def value (self, + batch) : + return self.start_lr_ * np.power (self.decay_rate_, (batch // self.decay_steps())) + + def decay_steps (self) : +# return self.decay_steps_ * self.tot_numb_batches + return self.decay_steps_ + + def decay_rate (self) : + return self.decay_rate_ + + def start_lr (self) : + return self.start_lr_ + +class NNPTrainer (object): + def __init__(self, + jdata, + run_opt): + self.run_opt = run_opt + self._init_param(jdata) + + def _init_param(self, jdata): + # descrpt config + self.use_smooth = False + if j_have (jdata, "use_smooth") : + self.use_smooth = jdata["use_smooth"] + if self.use_smooth: + self.model = ModelSeA(jdata) + else : + self.model = ModelLocFrame(jdata) + + self.numb_test = j_must_have (jdata, 'numb_test') + self.useBN = False + + self.start_pref_e = j_must_have (jdata, 'start_pref_e') + self.limit_pref_e = j_must_have (jdata, 'limit_pref_e') + self.start_pref_f = j_must_have (jdata, 'start_pref_f') + self.limit_pref_f = j_must_have (jdata, 'limit_pref_f') + self.start_pref_v = j_must_have (jdata, 'start_pref_v') + self.limit_pref_v = j_must_have (jdata, 'limit_pref_v') + self.start_pref_ae = 0 + if j_have(jdata, 'start_pref_ae') : + self.start_pref_ae = jdata['start_pref_ae'] + self.limit_pref_ae = 0 + if j_have(jdata, 'limit_pref_ae') : + self.limit_pref_ae = jdata['limit_pref_ae'] + self.has_e = (self.start_pref_e != 0 or self.limit_pref_e != 0) + self.has_f = (self.start_pref_f != 0 or self.limit_pref_f != 0) + self.has_v = (self.start_pref_v != 0 or self.limit_pref_v != 0) + self.has_ae = (self.start_pref_ae != 0 or self.limit_pref_ae != 0) + + self.disp_file = "lcurve.out" + if j_have (jdata, "disp_file") : self.disp_file = jdata["disp_file"] + self.disp_freq = j_must_have (jdata, 'disp_freq') + self.save_freq = j_must_have (jdata, 'save_freq') + self.save_ckpt = j_must_have (jdata, 'save_ckpt') + + self.display_in_training = j_must_have (jdata, 'disp_training') + self.timing_in_training = j_must_have (jdata, 'time_training') + self.profiling = False + if j_have (jdata, 'profiling') : + self.profiling = jdata['profiling'] + if self.profiling : + self.profiling_file = j_must_have (jdata, 'profiling_file') + + self.sys_weights = None + if j_have(jdata, 'sys_weights') : + self.sys_weights = jdata['sys_weights'] + + + def _message (self, msg) : + self.run_opt.message(msg) + + def build (self, + data, + lr) : + self.lr = lr + self.ntypes = self.model.ntypes + assert (self.ntypes == data.get_ntypes()), "ntypes should match that found in data" + + self.batch_size = data.get_batch_size() + + self.numb_fparam = data.numb_fparam() + if self.numb_fparam > 0 : + self._message("training with %d frame parameter(s)" % self.numb_fparam) + elif self.numb_fparam < 0 : + self._message("training without frame parameter") + else : + raise RuntimeError("number of frame parameter == 0") + + self.type_map = data.get_type_map() + + davg, dstd, bias_e = self.model.data_stat(data) + + worker_device = "/job:%s/task:%d/%s" % (self.run_opt.my_job_name, + self.run_opt.my_task_index, + self.run_opt.my_device) + + with tf.device(tf.train.replica_device_setter(worker_device = worker_device, + cluster = self.run_opt.cluster_spec)): + self._build_lr(lr) + self._build_network(davg, dstd, bias_e) + self._build_training() + + + def _build_lr(self, lr): + self._extra_train_ops = [] + self.global_step = tf.train.get_or_create_global_step() + self.starter_learning_rate = lr.start_lr() + self.learning_rate = tf.train.exponential_decay(lr.start_lr(), + self.global_step, + lr.decay_steps(), + lr.decay_rate(), + staircase=True) + self._message("built lr") + + def _build_network(self, davg, dstd, bias_atom_e): + + self.t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') + self.t_energy = tf.placeholder(global_ener_float_precision, [None], name='t_energy') + self.t_force = tf.placeholder(global_tf_float_precision, [None], name='t_force') + self.t_virial = tf.placeholder(global_tf_float_precision, [None], name='t_virial') + self.t_atom_ener = tf.placeholder(global_tf_float_precision, [None], name='t_atom_ener') + self.t_coord = tf.placeholder(global_tf_float_precision, [None], name='i_coord') + self.t_type = tf.placeholder(tf.int32, [None], name='i_type') + self.t_natoms = tf.placeholder(tf.int32, [self.ntypes+2], name='i_natoms') + self.t_box = tf.placeholder(global_tf_float_precision, [None, 9], name='i_box') + self.t_mesh = tf.placeholder(tf.int32, [None], name='i_mesh') + self.is_training = tf.placeholder(tf.bool) + if self.numb_fparam > 0 : + self.t_fparam = tf.placeholder(global_tf_float_precision, [None], name='i_fparam') + else : + self.t_fparam = None + + self.energy, self.force, self.virial, self.atom_ener \ + = self.model.build_interaction (self.t_coord, + self.t_type, + self.t_natoms, + self.t_box, + self.t_mesh, + self.t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "", + reuse_attr = False, + reuse_weights = False) + + self.l2_l, self.l2_el, self.l2_fl, self.l2_vl, self.l2_ael \ + = self.loss (self.t_natoms, \ + self.t_prop_c, \ + self.t_energy, self.energy, \ + self.t_force, self.force, \ + self.t_virial, self.virial, \ + self.t_atom_ener, self.atom_ener, \ + suffix = "test") + + self._message("built network") + + def _build_training(self): + trainable_variables = tf.trainable_variables() + optimizer = tf.train.AdamOptimizer(learning_rate = self.learning_rate) + if self.run_opt.is_distrib : + optimizer = tf.train.SyncReplicasOptimizer( + optimizer, + replicas_to_aggregate = self.run_opt.cluster_spec.num_tasks("worker"), + total_num_replicas = self.run_opt.cluster_spec.num_tasks("worker"), + name = "sync_replicas") + self.sync_replicas_hook = optimizer.make_session_run_hook(self.run_opt.is_chief) + grads = tf.gradients(self.l2_l, trainable_variables) + apply_op = optimizer.apply_gradients (zip (grads, trainable_variables), + global_step=self.global_step, + name='train_step') + train_ops = [apply_op] + self._extra_train_ops + self.train_op = tf.group(*train_ops) + self._message("built training") + + def _init_sess_serial(self) : + self.sess = tf.Session( + config=tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, + inter_op_parallelism_threads=self.run_opt.num_inter_threads + )) + self.saver = tf.train.Saver() + saver = self.saver + if self.run_opt.init_mode == 'init_from_scratch' : + self._message("initialize model from scratch") + init_op = tf.global_variables_initializer() + self.sess.run(init_op) + fp = open(self.disp_file, "w") + fp.close () + elif self.run_opt.init_mode == 'init_from_model' : + self._message("initialize from model %s" % self.run_opt.init_model) + init_op = tf.global_variables_initializer() + self.sess.run(init_op) + saver.restore (self.sess, self.run_opt.init_model) + self.sess.run(self.global_step.assign(0)) + fp = open(self.disp_file, "w") + fp.close () + elif self.run_opt.init_mode == 'restart' : + self._message("restart from model %s" % self.run_opt.restart) + init_op = tf.global_variables_initializer() + self.sess.run(init_op) + saver.restore (self.sess, self.run_opt.restart) + else : + raise RuntimeError ("unkown init mode") + + def _init_sess_distrib(self): + ckpt_dir = os.path.join(os.getcwd(), self.save_ckpt) + assert(_is_subdir(ckpt_dir, os.getcwd())), "the checkpoint dir must be a subdir of the current dir" + if self.run_opt.init_mode == 'init_from_scratch' : + self._message("initialize model from scratch") + if self.run_opt.is_chief : + if os.path.exists(ckpt_dir): + shutil.rmtree(ckpt_dir) + if not os.path.exists(ckpt_dir) : + os.makedirs(ckpt_dir) + fp = open(self.disp_file, "w") + fp.close () + elif self.run_opt.init_mode == 'init_from_model' : + raise RuntimeError("distributed training does not support %s" % self.run_opt.init_mode) + elif self.run_opt.init_mode == 'restart' : + self._message("restart from model %s" % ckpt_dir) + if self.run_opt.is_chief : + assert(os.path.isdir(ckpt_dir)), "the checkpoint dir %s should exists" % ckpt_dir + else : + raise RuntimeError ("unkown init mode") + + saver = tf.train.Saver(max_to_keep = 1) + self.saver = None + # gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.5) + # config = tf.ConfigProto(allow_soft_placement=True, + # gpu_options = gpu_options, + # intra_op_parallelism_threads=self.run_opt.num_intra_threads, + # inter_op_parallelism_threads=self.run_opt.num_inter_threads) + config = tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, + inter_op_parallelism_threads=self.run_opt.num_inter_threads) + # The stop_hook handles stopping after running given steps + # stop_hook = tf.train.StopAtStepHook(last_step = stop_batch) + # hooks = [self.sync_replicas_hook, stop_hook] + hooks = [self.sync_replicas_hook] + scaffold = tf.train.Scaffold(saver=saver) + # Use monitor session for distributed computation + self.sess = tf.train.MonitoredTrainingSession(master = self.run_opt.server.target, + is_chief = self.run_opt.is_chief, + config = config, + hooks = hooks, + scaffold = scaffold, + checkpoint_dir = ckpt_dir) + # , + # save_checkpoint_steps = self.save_freq) + + def train (self, + data, + stop_batch) : + if self.run_opt.is_distrib : + self._init_sess_distrib() + else : + self._init_sess_serial() + + self.print_head() + fp = None + if self.run_opt.is_chief : + fp = open(self.disp_file, "a") + + cur_batch = self.sess.run(self.global_step) + self.cur_batch = cur_batch + self.run_opt.message("start training at lr %.2e (== %.2e), final lr will be %.2e" % + (self.sess.run(self.learning_rate), + self.lr.value(cur_batch), + self.lr.value(stop_batch)) + ) + + prf_options = None + prf_run_metadata = None + if self.profiling : + prf_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE) + prf_run_metadata = tf.RunMetadata() + + train_time = 0 + while cur_batch < stop_batch : + batch_prop_c, \ + batch_energy, batch_force, batch_virial, batch_atom_ener, \ + batch_coord, batch_box, batch_type, batch_fparam, \ + natoms_vec, \ + default_mesh \ + = data.get_batch (sys_weights = self.sys_weights) + cur_batch_size = batch_energy.shape[0] + feed_dict_batch = {self.t_prop_c: batch_prop_c, + self.t_energy: batch_energy, + self.t_force: np.reshape(batch_force, [-1]), + self.t_virial: np.reshape(batch_virial, [-1]), + self.t_atom_ener: np.reshape(batch_atom_ener, [-1]), + self.t_coord: np.reshape(batch_coord, [-1]), + self.t_box: batch_box, + self.t_type: np.reshape(batch_type, [-1]), + self.t_natoms: natoms_vec, + self.t_mesh: default_mesh, + self.is_training: True} + if self.numb_fparam > 0 : + feed_dict_batch[self.t_fparam] = np.reshape(batch_fparam, [-1]) + if self.display_in_training and cur_batch == 0 : + self.test_on_the_fly(fp, data, feed_dict_batch) + if self.timing_in_training : tic = time.time() + self.sess.run([self.train_op], feed_dict = feed_dict_batch, options=prf_options, run_metadata=prf_run_metadata) + if self.timing_in_training : toc = time.time() + if self.timing_in_training : train_time += toc - tic + cur_batch = self.sess.run(self.global_step) + self.cur_batch = cur_batch + + if self.display_in_training and (cur_batch % self.disp_freq == 0) : + tic = time.time() + self.test_on_the_fly(fp, data, feed_dict_batch) + toc = time.time() + test_time = toc - tic + if self.timing_in_training : + self._message("batch %7d training time %.2f s, testing time %.2f s" + % (cur_batch, train_time, test_time)) + train_time = 0 + if self.save_freq > 0 and cur_batch % self.save_freq == 0 and self.run_opt.is_chief : + if self.saver is not None : + self.saver.save (self.sess, os.getcwd() + "/" + self.save_ckpt) + self._message("saved checkpoint %s" % self.save_ckpt) + if self.run_opt.is_chief: + fp.close () + if self.profiling and self.run_opt.is_chief : + fetched_timeline = timeline.Timeline(prf_run_metadata.step_stats) + chrome_trace = fetched_timeline.generate_chrome_trace_format() + with open(self.profiling_file, 'w') as f: + f.write(chrome_trace) + + def get_global_step (self) : + return self.sess.run(self.global_step) + + def print_head (self) : + if self.run_opt.is_chief: + fp = open(self.disp_file, "a") + print_str = "# %5s" % 'batch' + prop_fmt = ' %9s %9s' + print_str += prop_fmt % ('l2_tst', 'l2_trn') + if self.has_e : + print_str += prop_fmt % ('l2_e_tst', 'l2_e_trn') + if self.has_ae : + print_str += prop_fmt % ('l2_ae_tst', 'l2_ae_trn') + if self.has_f : + print_str += prop_fmt % ('l2_f_tst', 'l2_f_trn') + if self.has_v : + print_str += prop_fmt % ('l2_v_tst', 'l2_v_trn') + print_str += ' %8s\n' % 'lr' + fp.write(print_str) + fp.close () + + def test_on_the_fly (self, + fp, + data, + feed_dict_batch) : + test_prop_c, \ + test_energy, test_force, test_virial, test_atom_ener, \ + test_coord, test_box, test_type, test_fparam, \ + natoms_vec, \ + default_mesh \ + = data.get_test () + feed_dict_test = {self.t_prop_c: test_prop_c, + self.t_energy: test_energy [:self.numb_test], + self.t_force: np.reshape(test_force [:self.numb_test, :], [-1]), + self.t_virial: np.reshape(test_virial [:self.numb_test, :], [-1]), + self.t_atom_ener: np.reshape(test_atom_ener[:self.numb_test, :], [-1]), + self.t_coord: np.reshape(test_coord [:self.numb_test, :], [-1]), + self.t_box: test_box [:self.numb_test, :], + self.t_type: np.reshape(test_type [:self.numb_test, :], [-1]), + self.t_natoms: natoms_vec, + self.t_mesh: default_mesh, + self.is_training: False} + if self.numb_fparam > 0 : + feed_dict_test[self.t_fparam] = np.reshape(test_fparam [:self.numb_test, :], [-1]) + error_test, error_e_test, error_f_test, error_v_test, error_ae_test \ + = self.sess.run([self.l2_l, \ + self.l2_el, \ + self.l2_fl, \ + self.l2_vl, \ + self.l2_ael], + feed_dict=feed_dict_test) + error_train, error_e_train, error_f_train, error_v_train, error_ae_train \ + = self.sess.run([self.l2_l, \ + self.l2_el, \ + self.l2_fl, \ + self.l2_vl, \ + self.l2_ael], + feed_dict=feed_dict_batch) + cur_batch = self.cur_batch + current_lr = self.sess.run(self.learning_rate) + if self.run_opt.is_chief: + print_str = "%7d" % cur_batch + prop_fmt = " %9.2e %9.2e" + print_str += prop_fmt % (np.sqrt(error_test), np.sqrt(error_train)) + if self.has_e : + print_str += prop_fmt % (np.sqrt(error_e_test) / natoms_vec[0], np.sqrt(error_e_train) / natoms_vec[0]) + if self.has_ae : + print_str += prop_fmt % (np.sqrt(error_ae_test), np.sqrt(error_ae_train)) + if self.has_f : + print_str += prop_fmt % (np.sqrt(error_f_test), np.sqrt(error_f_train)) + if self.has_v : + print_str += prop_fmt % (np.sqrt(error_v_test) / natoms_vec[0], np.sqrt(error_v_train) / natoms_vec[0]) + print_str += " %8.1e\n" % current_lr + fp.write(print_str) + fp.flush () + + def loss (self, + natoms, + prop_c, + energy, + energy_hat, + force, + force_hat, + virial, + virial_hat, + atom_ener, + atom_ener_hat, + suffix): + l2_ener_loss = tf.reduce_mean( tf.square(energy - energy_hat), name='l2_'+suffix) + + force_reshape = tf.reshape (force, [-1]) + force_hat_reshape = tf.reshape (force_hat, [-1]) + l2_force_loss = tf.reduce_mean (tf.square(force_hat_reshape - force_reshape), name = "l2_force_" + suffix) + + virial_reshape = tf.reshape (virial, [-1]) + virial_hat_reshape = tf.reshape (virial_hat, [-1]) + l2_virial_loss = tf.reduce_mean (tf.square(virial_hat_reshape - virial_reshape), name = "l2_virial_" + suffix) + + atom_ener_reshape = tf.reshape (atom_ener, [-1]) + atom_ener_hat_reshape = tf.reshape (atom_ener_hat, [-1]) + l2_atom_ener_loss = tf.reduce_mean (tf.square(atom_ener_hat_reshape - atom_ener_reshape), name = "l2_atom_ener_" + suffix) + + atom_norm = 1./ global_cvt_2_tf_float(natoms[0]) + atom_norm_ener = 1./ global_cvt_2_ener_float(natoms[0]) + pref_e = global_cvt_2_ener_float(prop_c[0] * (self.limit_pref_e + (self.start_pref_e - self.limit_pref_e) * self.learning_rate / self.starter_learning_rate) ) + pref_f = global_cvt_2_tf_float(prop_c[1] * (self.limit_pref_f + (self.start_pref_f - self.limit_pref_f) * self.learning_rate / self.starter_learning_rate) ) + pref_v = global_cvt_2_tf_float(prop_c[2] * (self.limit_pref_v + (self.start_pref_v - self.limit_pref_v) * self.learning_rate / self.starter_learning_rate) ) + pref_ae= global_cvt_2_tf_float(prop_c[3] * (self.limit_pref_ae+ (self.start_pref_ae-self.limit_pref_ae) * self.learning_rate / self.starter_learning_rate) ) + + l2_loss = 0 + if self.has_e : + l2_loss += atom_norm_ener * (pref_e * l2_ener_loss) + if self.has_f : + l2_loss += global_cvt_2_ener_float(pref_f * l2_force_loss) + if self.has_v : + l2_loss += global_cvt_2_ener_float(atom_norm * (pref_v * l2_virial_loss)) + if self.has_ae : + l2_loss += global_cvt_2_ener_float(pref_ae * l2_atom_ener_loss) + + return l2_loss, l2_ener_loss, l2_force_loss, l2_virial_loss, l2_atom_ener_loss + diff --git a/source/train/train.py b/source/train/train.py index 314e132552..c8603b35e8 100755 --- a/source/train/train.py +++ b/source/train/train.py @@ -13,8 +13,8 @@ from deepmd.RunOptions import RunOptions from deepmd.DataSystem import DataSystem -from deepmd.Model import NNPModel -from deepmd.Model import LearingRate +from deepmd.Trainer import NNPTrainer +from deepmd.Trainer import LearingRate def create_done_queue(cluster_spec, task_index): with tf.device("/job:ps/task:%d" % (task_index)): @@ -96,7 +96,7 @@ def _do_work(jdata, run_opt): tot_numb_batches = sum(data.get_nbatches()) lr = LearingRate (jdata, tot_numb_batches) # init the model - model = NNPModel (jdata, run_opt = run_opt) + model = NNPTrainer (jdata, run_opt = run_opt) # build the model with stats from the first system model.build (data, lr) # train the model with the provided systems in a cyclic way From dbfad898b6669acc5ccb92405edbc4dacb4a4203 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Sun, 23 Jun 2019 09:10:55 +0800 Subject: [PATCH 10/33] implement model se_r and se_ar --- examples/train/water.json | 27 +- examples/train/water_se_a.json | 51 ++ examples/train/water_se_ar.json | 61 +++ source/tests/test_model_loc_frame.py | 2 +- source/tests/test_model_se_a.py | 4 +- source/tests/test_model_se_r.py | 126 +++++ .../{water_smth.json => water_se_a.json} | 0 .../tests/water_se_r.json | 7 +- source/train/ModelHyb.py | 101 ++++ source/train/ModelLocFrame.py | 23 +- source/train/ModelSeA.py | 15 +- source/train/ModelSeR.py | 455 ++++++++++++++++++ source/train/Trainer.py | 29 +- source/train/train.py | 9 +- 14 files changed, 873 insertions(+), 37 deletions(-) create mode 100644 examples/train/water_se_a.json create mode 100644 examples/train/water_se_ar.json create mode 100644 source/tests/test_model_se_r.py rename source/tests/{water_smth.json => water_se_a.json} (100%) rename examples/train/water_smth.json => source/tests/water_se_r.json (90%) create mode 100644 source/train/ModelHyb.py create mode 100644 source/train/ModelSeR.py diff --git a/examples/train/water.json b/examples/train/water.json index 6e28744ddf..4de81e3031 100644 --- a/examples/train/water.json +++ b/examples/train/water.json @@ -1,18 +1,21 @@ { "with_distrib": false, "_comment": " model parameters", - "use_smooth": false, - "sel_a": [16, 32], - "sel_r": [30, 60], - "rcut": 6.00, - "axis_rule": [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0], - "_comment": " default rule: []", - "_comment": " user defined rule: for each type provides two axes, ", - "_comment": " for each axis: (a_or_r, type, idx)", - "_comment": " if type < 0, exclude type -(type+1)", - "_comment": " for water (O:0, H:1) it can be", - "_comment": " [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0]", - "fitting_neuron": [240, 120, 60, 30, 10], + "model_type": "loc_frame", + "model":{ + "sel_a": [16, 32], + "sel_r": [30, 60], + "rcut": 6.00, + "axis_rule": [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0], + "_comment": " default rule: []", + "_comment": " user defined rule: for each type provides two axes, ", + "_comment": " for each axis: (a_or_r, type, idx)", + "_comment": " if type < 0, exclude type -(type+1)", + "_comment": " for water (O:0, H:1) it can be", + "_comment": " [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0]", + "fitting_neuron": [240, 240, 240], + "seed": 1 + }, "_comment": " traing controls", "systems": ["../data/water/"], diff --git a/examples/train/water_se_a.json b/examples/train/water_se_a.json new file mode 100644 index 0000000000..c6a818385c --- /dev/null +++ b/examples/train/water_se_a.json @@ -0,0 +1,51 @@ +{ + "_comment": " model parameters", + "model_type": "se_a", + "model": { + "sel_a": [46, 92], + "rcut_smth": 5.80, + "rcut": 6.00, + "filter_neuron": [25, 50, 100], + "filter_resnet_dt": false, + "axis_neuron": 16, + "fitting_neuron": [240, 240, 240], + "fitting_resnet_dt": true, + "coord_norm": true, + "type_fitting_net": false, + "seed": 1 + }, + + "_comment": " traing controls", + "systems": ["../data/water/"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 1, + "start_lr": 0.005, + "decay_steps": 5000, + "decay_rate": 0.95, + + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + + "seed": 1, + + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 10, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training": true, + "time_training": true, + "profiling": false, + "profiling_file": "timeline.json", + + "_comment": "that's all" +} + diff --git a/examples/train/water_se_ar.json b/examples/train/water_se_ar.json new file mode 100644 index 0000000000..4a10c48047 --- /dev/null +++ b/examples/train/water_se_ar.json @@ -0,0 +1,61 @@ +{ + "_comment": " model parameters", + "model_type": "se_ar", + "model_a": { + "sel_a": [16, 32], + "rcut_smth": 1.00, + "rcut": 3.80, + "filter_neuron": [10, 20, 40], + "filter_resnet_dt": false, + "axis_neuron": 16, + "fitting_neuron": [120, 120, 120], + "fitting_resnet_dt": true, + "seed": 1, + "_comment": "that's all" + }, + "model_r": { + "sel_r": [46, 92], + "rcut_smth": 1.00, + "rcut": 6.00, + "filter_neuron": [5, 10, 20], + "filter_resnet_dt": false, + "fitting_neuron": [120, 120, 120], + "fitting_resnet_dt": true, + "seed": 1, + "_comment": "that's all" + }, + + "_comment": " traing controls", + "systems": ["../data/water/"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 1, + "start_lr": 0.005, + "decay_steps": 5000, + "decay_rate": 0.95, + + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + + "seed": 1, + + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 10, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training": true, + "time_training": true, + "profiling": false, + "profiling_file": "timeline.json", + + "_comment": "that's all" +} + diff --git a/source/tests/test_model_loc_frame.py b/source/tests/test_model_loc_frame.py index 56231c5dee..67234b42fa 100644 --- a/source/tests/test_model_loc_frame.py +++ b/source/tests/test_model_loc_frame.py @@ -76,7 +76,7 @@ def test_model(self): is_training = tf.placeholder(tf.bool) t_fparam = None - energy, force, virial, atom_ener \ + energy, force, virial, atom_ener, atom_virial \ = model.build_interaction (t_coord, t_type, t_natoms, diff --git a/source/tests/test_model_se_a.py b/source/tests/test_model_se_a.py index f247601ebd..333a196089 100644 --- a/source/tests/test_model_se_a.py +++ b/source/tests/test_model_se_a.py @@ -35,7 +35,7 @@ def setUp(self) : gen_data() def test_model(self): - jfile = 'water_smth.json' + jfile = 'water_se_a.json' with open(jfile) as fp: jdata = json.load (fp) run_opt = RunOptions(None) @@ -76,7 +76,7 @@ def test_model(self): is_training = tf.placeholder(tf.bool) t_fparam = None - energy, force, virial, atom_ener \ + energy, force, virial, atom_ener, atom_virial \ = model.build_interaction (t_coord, t_type, t_natoms, diff --git a/source/tests/test_model_se_r.py b/source/tests/test_model_se_r.py new file mode 100644 index 0000000000..fae15df1a5 --- /dev/null +++ b/source/tests/test_model_se_r.py @@ -0,0 +1,126 @@ +import dpdata,os,sys,json,unittest +import numpy as np +import tensorflow as tf +from common import Data + +lib_path = os.path.dirname(os.path.realpath(__file__)) + ".." +sys.path.append (lib_path) + +from deepmd.RunOptions import RunOptions +from deepmd.DataSystem import DataSystem +from deepmd.ModelSeR import ModelSeR +from deepmd.common import j_must_have, j_must_have_d, j_have + +global_ener_float_precision = tf.float64 +global_tf_float_precision = tf.float64 +global_np_float_precision = np.float64 + +def gen_data() : + tmpdata = Data(rand_pert = 0.1, seed = 1) + sys = dpdata.LabeledSystem() + sys.data['coords'] = tmpdata.coord + sys.data['atom_types'] = tmpdata.atype + sys.data['cells'] = tmpdata.cell + nframes = tmpdata.nframes + natoms = tmpdata.natoms + sys.data['coords'] = sys.data['coords'].reshape([nframes,natoms,3]) + sys.data['cells'] = sys.data['cells'].reshape([nframes,3,3]) + sys.data['energies'] = np.zeros([nframes,1]) + sys.data['forces'] = np.zeros([nframes,natoms,3]) + sys.data['virials'] = [] + sys.to_deepmd_npy('system', prec=np.float64) + +class TestModel(unittest.TestCase): + def setUp(self) : + gen_data() + + def test_model(self): + jfile = 'water_se_r.json' + with open(jfile) as fp: + jdata = json.load (fp) + run_opt = RunOptions(None) + systems = j_must_have(jdata, 'systems') + set_pfx = j_must_have(jdata, 'set_prefix') + batch_size = j_must_have(jdata, 'batch_size') + test_size = j_must_have(jdata, 'numb_test') + batch_size = 1 + test_size = 1 + stop_batch = j_must_have(jdata, 'stop_batch') + rcut = j_must_have (jdata, 'rcut') + + data = DataSystem(systems, set_pfx, batch_size, test_size, rcut, run_opt = None) + + test_prop_c, \ + test_energy, test_force, test_virial, test_atom_ener, \ + test_coord, test_box, test_type, test_fparam, \ + natoms_vec, \ + default_mesh \ + = data.get_test () + numb_test = 1 + + bias_atom_e = data.compute_energy_shift() + + model = ModelSeR(jdata) + davg, dstd = model.compute_dstats([test_coord], [test_box], [test_type], [natoms_vec], [default_mesh]) + + t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') + t_energy = tf.placeholder(global_ener_float_precision, [None], name='t_energy') + t_force = tf.placeholder(global_tf_float_precision, [None], name='t_force') + t_virial = tf.placeholder(global_tf_float_precision, [None], name='t_virial') + t_atom_ener = tf.placeholder(global_tf_float_precision, [None], name='t_atom_ener') + t_coord = tf.placeholder(global_tf_float_precision, [None], name='i_coord') + t_type = tf.placeholder(tf.int32, [None], name='i_type') + t_natoms = tf.placeholder(tf.int32, [model.ntypes+2], name='i_natoms') + t_box = tf.placeholder(global_tf_float_precision, [None, 9], name='i_box') + t_mesh = tf.placeholder(tf.int32, [None], name='i_mesh') + is_training = tf.placeholder(tf.bool) + t_fparam = None + + energy, force, virial, atom_ener, atom_virial \ + = model.build_interaction (t_coord, + t_type, + t_natoms, + t_box, + t_mesh, + t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "se_r", + reuse_attr = False, + reuse_weights = False) + + feed_dict_test = {t_prop_c: test_prop_c, + t_energy: test_energy [:numb_test], + t_force: np.reshape(test_force [:numb_test, :], [-1]), + t_virial: np.reshape(test_virial [:numb_test, :], [-1]), + t_atom_ener: np.reshape(test_atom_ener[:numb_test, :], [-1]), + t_coord: np.reshape(test_coord [:numb_test, :], [-1]), + t_box: test_box [:numb_test, :], + t_type: np.reshape(test_type [:numb_test, :], [-1]), + t_natoms: natoms_vec, + t_mesh: default_mesh, + is_training: False} + + sess = tf.Session() + sess.run(tf.global_variables_initializer()) + [e, f, v] = sess.run([energy, force, virial], + feed_dict = feed_dict_test) + + e = e.reshape([-1]) + f = f.reshape([-1]) + v = v.reshape([-1]) + refe = [6.152085988309423925e+01] + reff = [-1.714443151616400110e-04,-1.315836609370952051e-04,-5.584120460897444674e-06,-7.197863450669731334e-05,-1.384609799994930676e-04,8.856091902774708468e-06,1.120578238869146797e-04,-7.428703645877488470e-05,9.370560731488587317e-07,-1.048347129617610465e-04,1.977876923815685781e-04,7.522050342771599598e-06,2.361772659657814205e-04,-5.774651813388292487e-05,-1.233143271630744828e-05,2.257277740226381951e-08,2.042905031476775584e-04,6.003548585097267914e-07] + refv = [1.035180911513190792e-03,-1.118982949050497126e-04,-2.383287813436022850e-05,-1.118982949050497126e-04,4.362023915782403281e-04,8.119543218224559240e-06,-2.383287813436022850e-05,8.119543218224559240e-06,1.201142938802945237e-06] + refe = np.reshape(refe, [-1]) + reff = np.reshape(reff, [-1]) + refv = np.reshape(refv, [-1]) + + places = 6 + for ii in range(e.size) : + self.assertAlmostEqual(e[ii], refe[ii], places = places) + for ii in range(f.size) : + self.assertAlmostEqual(f[ii], reff[ii], places = places) + for ii in range(v.size) : + self.assertAlmostEqual(v[ii], refv[ii], places = places) diff --git a/source/tests/water_smth.json b/source/tests/water_se_a.json similarity index 100% rename from source/tests/water_smth.json rename to source/tests/water_se_a.json diff --git a/examples/train/water_smth.json b/source/tests/water_se_r.json similarity index 90% rename from examples/train/water_smth.json rename to source/tests/water_se_r.json index e4a639de0f..9c7b1f268a 100644 --- a/examples/train/water_smth.json +++ b/source/tests/water_se_r.json @@ -1,19 +1,18 @@ { "_comment": " model parameters", "use_smooth": true, - "sel_a": [46, 92], + "sel_r": [46, 92], "rcut_smth": 5.80, "rcut": 6.00, "filter_neuron": [25, 50, 100], "filter_resnet_dt": false, - "axis_neuron": 16, "fitting_neuron": [240, 240, 240], "fitting_resnet_dt":true, "coord_norm": true, "type_fitting_net": false, "_comment": " traing controls", - "systems": ["../data/water/"], + "systems": ["system"], "set_prefix": "set", "stop_batch": 1000000, "batch_size": 1, @@ -34,7 +33,7 @@ "_comment": " frequencies counted in batch", "disp_file": "lcurve.out", "disp_freq": 100, - "numb_test": 10, + "numb_test": 1, "save_freq": 1000, "save_ckpt": "model.ckpt", "load_ckpt": "model.ckpt", diff --git a/source/train/ModelHyb.py b/source/train/ModelHyb.py new file mode 100644 index 0000000000..714320cd4e --- /dev/null +++ b/source/train/ModelHyb.py @@ -0,0 +1,101 @@ +import os,warnings +import numpy as np +import tensorflow as tf +from deepmd.common import j_must_have, j_must_have_d, j_have +from deepmd.Model import Model +from deepmd.ModelSeA import ModelSeA +from deepmd.ModelSeR import ModelSeR + +from deepmd.RunOptions import global_tf_float_precision +from deepmd.RunOptions import global_np_float_precision +from deepmd.RunOptions import global_ener_float_precision +from deepmd.RunOptions import global_cvt_2_tf_float +from deepmd.RunOptions import global_cvt_2_ener_float + +module_path = os.path.dirname(os.path.realpath(__file__)) + "/" +assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" +op_module = tf.load_op_library(module_path + "libop_abi.so") + +class ModelHyb() : + def __init__ (self, jdata_a, jdata_r): + self.model_a = ModelSeA(jdata_a) + self.model_r = ModelSeR(jdata_r) + + def get_rcut(self): + return self.model_r.get_rcut() + + def get_ntypes(self): + return self.model_r.get_ntypes() + + def data_stat(self, data): + all_stat_coord = [] + all_stat_box = [] + all_stat_type = [] + all_natoms_vec = [] + all_default_mesh = [] + for ii in range(data.get_nsystems()) : + stat_prop_c, \ + stat_energy, stat_force, stat_virial, start_atom_ener, \ + stat_coord, stat_box, stat_type, stat_fparam, natoms_vec, default_mesh \ + = data.get_batch (sys_idx = ii) + natoms_vec = natoms_vec.astype(np.int32) + all_stat_coord.append(stat_coord) + all_stat_box.append(stat_box) + all_stat_type.append(stat_type) + all_natoms_vec.append(natoms_vec) + all_default_mesh.append(default_mesh) + + davg, dstd = self.compute_dstats (all_stat_coord, all_stat_box, all_stat_type, all_natoms_vec, all_default_mesh) + + bias_atom_e = data.compute_energy_shift() + + return davg, dstd, bias_atom_e + + def compute_dstats (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + davg_a, dstd_a = self.model_a.compute_dstats(data_coord, data_box, data_atype, natoms_vec, mesh, reuse) + davg_r, dstd_r = self.model_r.compute_dstats(data_coord, data_box, data_atype, natoms_vec, mesh, reuse) + return [davg_a, davg_r], [dstd_a, dstd_r] + + def build_interaction(self, + coord, + atype, + natoms, + box, + mesh, + fparam, + davg, + dstd, + bias_atom_e, + suffix = '', + reuse_attr = None, + reuse_weights = None) : + e_a, f_a, v_a, ae_a, av_a \ + = self.model_a.build_interaction(coord, atype, natoms, box, mesh, fparam, davg[0], dstd[0], bias_atom_e, suffix = '_a'+suffix, reuse_attr = reuse_attr, reuse_weights = reuse_weights) + e_r, f_r, v_r, ae_r, av_r \ + = self.model_r.build_interaction(coord, atype, natoms, box, mesh, fparam, davg[1], dstd[1], bias_atom_e, suffix = '_r'+suffix, reuse_attr = reuse_attr, reuse_weights = reuse_weights) + with tf.variable_scope('model_attr' + suffix, reuse = reuse_attr) : + t_rcut = tf.constant(self.model_r.get_rcut(), + name = 'rcut', + dtype = global_tf_float_precision) + t_ntypes = tf.constant(self.model_r.get_ntypes(), + name = 'ntypes', + dtype = tf.int32) + t_dfparam = tf.constant(self.model_r.get_numb_fparam(), + name = 'dfparam', + dtype = tf.int32) + t_tmap = tf.constant(' '.join(self.model_r.get_type_map()), + name = 'tmap', + dtype = tf.string) + + energy = tf.add(e_a, e_r, name = 'o_energy'+suffix) + force = tf.add(f_a, f_r, name = 'o_force' +suffix) + virial = tf.add(v_a, v_r, name = 'o_virial'+suffix) + ae = tf.add(ae_a, ae_r, name = 'o_atom_energy'+suffix) + av = tf.add(av_a, av_r, name = 'o_atom_virial'+suffix) + return energy, force, virial, ae, av diff --git a/source/train/ModelLocFrame.py b/source/train/ModelLocFrame.py index ba1eebfc11..4416f74283 100644 --- a/source/train/ModelLocFrame.py +++ b/source/train/ModelLocFrame.py @@ -67,6 +67,18 @@ def __init__(self, jdata) : self.seed = jdata['seed'] self.useBN = False + def get_rcut(self) : + return self.rcut_r + + def get_ntypes(self) : + return self.ntypes + + def get_numb_fparam (self) : + return self.numb_fparam + + def get_type_map (self) : + return self.type_map + def compute_dstats (self, data_coord, data_box, @@ -259,7 +271,7 @@ def build_interaction (self, virial = tf.reshape (virial, [-1, 9], name = "o_virial"+suffix) atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "o_atom_virial"+suffix) - return energy, force, virial, energy_raw + return energy, force, virial, energy_raw, atom_virial def build_atom_net (self, @@ -267,7 +279,8 @@ def build_atom_net (self, fparam, natoms, bias_atom_e = None, - reuse = None) : + reuse = None, + suffix = '') : start_index = 0 inputs = tf.reshape(inputs, [-1, self.ndescrpt * natoms[0]]) shape = inputs.get_shape().as_list() @@ -292,11 +305,11 @@ def build_atom_net (self, ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) layer = tf.concat([layer, ext_fparam], axis = 1) - layer = self.one_layer(inputs_i, self.n_neuron[0], name='layer_0_type_'+str(type_i), reuse=reuse, seed = self.seed) + layer = self.one_layer(inputs_i, self.n_neuron[0], name='layer_0_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) for ii in range(1,len(self.n_neuron)) : - layer = self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i), reuse=reuse, seed = self.seed) + layer = self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) - final_layer = self.one_layer(layer, 1, activation_fn = None, bavg = type_bias_ae, name='final_layer_type_'+str(type_i), reuse=reuse, seed = self.seed) + final_layer = self.one_layer(layer, 1, activation_fn = None, bavg = type_bias_ae, name='final_layer_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) final_layer = tf.reshape(final_layer, [-1, natoms[2+type_i]]) # final_layer = tf.cond (tf.equal(natoms[2+type_i], 0), lambda: tf.zeros((0, 0), dtype=global_tf_float_precision), lambda : tf.reshape(final_layer, [-1, natoms[2+type_i]])) diff --git a/source/train/ModelSeA.py b/source/train/ModelSeA.py index f921f5be37..bfc57291c0 100644 --- a/source/train/ModelSeA.py +++ b/source/train/ModelSeA.py @@ -26,6 +26,7 @@ def __init__ (self, jdata): assert(self.ntypes == len(self.sel_r)) self.rcut_a = -1 self.rcut_r = j_must_have (jdata, 'rcut') + self.rcut = self.rcut_r if j_have(jdata, 'rcut_smth') : self.rcut_r_smth = jdata['rcut_smth'] else : @@ -83,6 +84,18 @@ def __init__ (self, jdata): self.useBN = False + def get_rcut (self) : + return self.rcut + + def get_ntypes (self) : + return self.ntypes + + def get_numb_fparam (self) : + return self.numb_fparam + + def get_type_map (self) : + return self.type_map + def compute_dstats (self, data_coord, data_box, @@ -289,7 +302,7 @@ def build_interaction (self, virial = tf.reshape (virial, [-1, 9], name = "o_virial"+suffix) atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "o_atom_virial"+suffix) - return energy, force, virial, energy_raw + return energy, force, virial, energy_raw, atom_virial diff --git a/source/train/ModelSeR.py b/source/train/ModelSeR.py new file mode 100644 index 0000000000..11e1974587 --- /dev/null +++ b/source/train/ModelSeR.py @@ -0,0 +1,455 @@ +import os,warnings +import numpy as np +import tensorflow as tf +from deepmd.common import j_must_have, j_must_have_d, j_have +from deepmd.Model import Model + +from deepmd.RunOptions import global_tf_float_precision +from deepmd.RunOptions import global_np_float_precision +from deepmd.RunOptions import global_ener_float_precision +from deepmd.RunOptions import global_cvt_2_tf_float +from deepmd.RunOptions import global_cvt_2_ener_float + +module_path = os.path.dirname(os.path.realpath(__file__)) + "/" +assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" +op_module = tf.load_op_library(module_path + "libop_abi.so") + +class ModelSeR (Model): + def __init__ (self, jdata): + # descrpt config + self.use_smooth = False + self.sel_r = j_must_have (jdata, 'sel_r') + self.sel_a = [ 0 for ii in range(len(self.sel_r)) ] + self.sel = self.sel_r + if j_have (jdata, 'sel_a') : + warnings.warn ('ignoring key sel_a in the json database and set sel_r to %s' % str(self.sel_a)) + self.ntypes = len(self.sel_r) + self.rcut = j_must_have (jdata, 'rcut') + if j_have(jdata, 'rcut_smth') : + self.rcut_smth = jdata['rcut_smth'] + else : + self.rcut_smth = self.rcut + # fparam + self.numb_fparam = 0 + if j_have(jdata, 'numb_fparam') : + self.numb_fparam = jdata['numb_fparam'] + # type_map + self.type_map = [] + if j_have(jdata, 'type_map') : + self.numb_fparam = jdata['type_map'] + # filter of smooth version + if j_have(jdata, 'coord_norm') : + self.coord_norm = jdata['coord_norm'] + else : + self.coord_norm = True + self.filter_neuron = j_must_have (jdata, 'filter_neuron') + self.filter_resnet_dt = False + if j_have(jdata, 'filter_resnet_dt') : + self.filter_resnet_dt = jdata['filter_resnet_dt'] + # numb of neighbors and numb of descrptors + self.nnei_a = np.cumsum(self.sel_a)[-1] + self.nnei_r = np.cumsum(self.sel_r)[-1] + self.nnei = np.cumsum(self.sel)[-1] + self.ndescrpt_a = self.nnei_a * 4 + self.ndescrpt_r = self.nnei_r * 1 + self.ndescrpt = self.nnei_r + # network size + self.n_neuron = j_must_have_d (jdata, 'fitting_neuron', ['n_neuron']) + self.resnet_dt = True + if j_have(jdata, 'resnet_dt') : + warnings.warn("the key \"%s\" is deprecated, please use \"%s\" instead" % ('resnet_dt','fitting_resnet_dt')) + self.resnet_dt = jdata['resnet_dt'] + if j_have(jdata, 'fitting_resnet_dt') : + self.resnet_dt = jdata['fitting_resnet_dt'] + self.type_fitting_net = False + + # short-range tab + if 'use_srtab' in jdata : + self.srtab = TabInter(jdata['use_srtab']) + self.smin_alpha = j_must_have(jdata, 'smin_alpha') + self.sw_rmin = j_must_have(jdata, 'sw_rmin') + self.sw_rmax = j_must_have(jdata, 'sw_rmax') + else : + self.srtab = None + + self.seed = None + if j_have (jdata, 'seed') : + self.seed = jdata['seed'] + self.useBN = False + + def get_rcut (self) : + return self.rcut + + def get_ntypes (self) : + return self.ntypes + + def get_numb_fparam (self) : + return self.numb_fparam + + def get_type_map (self) : + return self.type_map + + def compute_dstats (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + all_davg = [] + all_dstd = [] + sumr = [] + sumn = [] + sumr2 = [] + for cc,bb,tt,nn,mm in zip(data_coord,data_box,data_atype,natoms_vec,mesh) : + sysr,sysr2,sysn \ + = self._compute_dstats_sys_se_r(cc,bb,tt,nn,mm,reuse) + sumr.append(sysr) + sumn.append(sysn) + sumr2.append(sysr2) + sumr = np.sum(sumr, axis = 0) + sumn = np.sum(sumn, axis = 0) + sumr2 = np.sum(sumr2, axis = 0) + for type_i in range(self.ntypes) : + davgunit = [sumr[type_i]/sumn[type_i]] + dstdunit = [self._compute_std(sumr2[type_i], sumr[type_i], sumn[type_i])] + davg = np.tile(davgunit, self.ndescrpt // 1) + dstd = np.tile(dstdunit, self.ndescrpt // 1) + all_davg.append(davg) + all_dstd.append(dstd) + + davg = np.array(all_davg) + dstd = np.array(all_dstd) + + return davg, dstd + + + def build_interaction (self, + coord_, + atype_, + natoms, + box, + mesh, + fparam, + davg = None, + dstd = None, + bias_atom_e = None, + suffix = '', + reuse_attr = None, + reuse_weights = None): + with tf.variable_scope('model_attr' + suffix, reuse = reuse_attr) : + if davg is None: + davg = np.zeros([self.ntypes, self.ndescrpt]) + if dstd is None: + dstd = np.ones ([self.ntypes, self.ndescrpt]) + t_rcut = tf.constant(self.rcut, + name = 'rcut', + dtype = global_tf_float_precision) + t_ntypes = tf.constant(self.ntypes, + name = 'ntypes', + dtype = tf.int32) + t_dfparam = tf.constant(self.numb_fparam, + name = 'dfparam', + dtype = tf.int32) + t_tmap = tf.constant(' '.join(self.type_map), + name = 'tmap', + dtype = tf.string) + self.t_avg = tf.get_variable('t_avg', + davg.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(davg, dtype = global_tf_float_precision)) + self.t_std = tf.get_variable('t_std', + dstd.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(dstd, dtype = global_tf_float_precision)) + if self.srtab is not None : + tab_info, tab_data = self.srtab.get() + self.tab_info = tf.get_variable('t_tab_info', + tab_info.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) + self.tab_data = tf.get_variable('t_tab_data', + tab_data.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) + + coord = tf.reshape (coord_, [-1, natoms[1] * 3]) + atype = tf.reshape (atype_, [-1, natoms[1]]) + + descrpt, descrpt_deriv, rij, nlist \ + = op_module.descrpt_se_r (coord, + atype, + natoms, + box, + mesh, + self.t_avg, + self.t_std, + rcut = self.rcut, + rcut_smth = self.rcut_smth, + sel = self.sel_r) + + descrpt_reshape = tf.reshape(descrpt, [-1, self.ndescrpt]) + + atom_ener = self.build_atom_net (descrpt_reshape, + fparam, + natoms, + bias_atom_e = bias_atom_e, + reuse = reuse_weights, + suffix = suffix) + + if self.srtab is not None : + sw_lambda, sw_deriv \ + = op_module.soft_min_switch(atype, + rij, + nlist, + natoms, + sel_a = self.sel_a, + sel_r = self.sel_r, + alpha = self.smin_alpha, + rmin = self.sw_rmin, + rmax = self.sw_rmax) + inv_sw_lambda = 1.0 - sw_lambda + # NOTICE: + # atom energy is not scaled, + # force and virial are scaled + tab_atom_ener, tab_force, tab_atom_virial \ + = op_module.tab_inter(self.tab_info, + self.tab_data, + atype, + rij, + nlist, + natoms, + sw_lambda, + sel_a = self.sel_a, + sel_r = self.sel_r) + energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, natoms[0]]) + tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) + atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener + energy_raw = tab_atom_ener + atom_ener + else : + energy_raw = atom_ener + + energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'o_atom_energy'+suffix) + energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='o_energy'+suffix) + + net_deriv_tmp = tf.gradients (atom_ener, descrpt_reshape) + net_deriv = net_deriv_tmp[0] + net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) + + force = op_module.prod_force_se_r (net_deriv_reshape, + descrpt_deriv, + nlist, + natoms) + if self.srtab is not None : + sw_force \ + = op_module.soft_min_force(energy_diff, + sw_deriv, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + force = force + sw_force + tab_force + + force = tf.reshape (force, [-1, 3 * natoms[1]], name = "o_force"+suffix) + + virial, atom_virial \ + = op_module.prod_virial_se_r (net_deriv_reshape, + descrpt_deriv, + rij, + nlist, + natoms) + if self.srtab is not None : + sw_virial, sw_atom_virial \ + = op_module.soft_min_virial (energy_diff, + sw_deriv, + rij, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + atom_virial = atom_virial + sw_atom_virial + tab_atom_virial + virial = virial + sw_virial \ + + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, natoms[1], 9]), axis = 1) + + virial = tf.reshape (virial, [-1, 9], name = "o_virial"+suffix) + atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "o_atom_virial"+suffix) + + return energy, force, virial, energy_raw, atom_virial + + + def build_atom_net (self, + inputs, + fparam, + natoms, + bias_atom_e = None, + reuse = None, + suffix = '') : + start_index = 0 + inputs = tf.reshape(inputs, [-1, self.ndescrpt * natoms[0]]) + shape = inputs.get_shape().as_list() + if bias_atom_e is not None : + assert(len(bias_atom_e) == self.ntypes) + + for type_i in range(self.ntypes): + # cut-out inputs + inputs_i = tf.slice (inputs, + [ 0, start_index* self.ndescrpt], + [-1, natoms[2+type_i]* self.ndescrpt] ) + inputs_i = tf.reshape(inputs_i, [-1, self.ndescrpt]) + start_index += natoms[2+type_i] + if bias_atom_e is None : + type_bias_ae = 0.0 + else : + type_bias_ae = bias_atom_e[type_i] + + # compute atom energy + layer = self._filter_r(inputs_i, name='filter_r_type_'+str(type_i)+suffix, natoms=natoms, reuse=reuse, seed = self.seed) + if self.numb_fparam > 0 : + ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) + ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) + ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) + layer = tf.concat([layer, ext_fparam], axis = 1) + for ii in range(0,len(self.n_neuron)) : + if ii >= 1 and self.n_neuron[ii] == self.n_neuron[ii-1] : + layer+= self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed, use_timestep = self.resnet_dt) + else : + layer = self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) + final_layer = self.one_layer(layer, 1, activation_fn = None, bavg = type_bias_ae, name='final_layer_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) + final_layer = tf.reshape(final_layer, [-1, natoms[2+type_i]]) + # final_layer = tf.cond (tf.equal(natoms[2+type_i], 0), lambda: tf.zeros((0, 0), dtype=global_tf_float_precision), lambda : tf.reshape(final_layer, [-1, natoms[2+type_i]])) + + # concat the results + if type_i == 0: + outs = final_layer + else: + outs = tf.concat([outs, final_layer], axis = 1) + + return tf.reshape(outs, [-1]) + + + def _compute_dstats_sys_se_r (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + avg_zero = np.zeros([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) + std_ones = np.ones ([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) + sub_graph = tf.Graph() + with sub_graph.as_default(): + descrpt, descrpt_deriv, rij, nlist \ + = op_module.descrpt_se_r (tf.constant(data_coord), + tf.constant(data_atype), + tf.constant(natoms_vec, dtype = tf.int32), + tf.constant(data_box), + tf.constant(mesh), + tf.constant(avg_zero), + tf.constant(std_ones), + rcut = self.rcut, + rcut_smth = self.rcut_smth, + sel = self.sel) + # sub_sess = tf.Session(graph = sub_graph, + # config=tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, + # inter_op_parallelism_threads=self.run_opt.num_inter_threads + + # )) + sub_sess = tf.Session(graph = sub_graph) + dd_all = sub_sess.run(descrpt) + sub_sess.close() + natoms = natoms_vec + dd_all = np.reshape(dd_all, [-1, self.ndescrpt * natoms[0]]) + start_index = 0 + sysr = [] + sysa = [] + sysn = [] + sysr2 = [] + sysa2 = [] + for type_i in range(self.ntypes): + end_index = start_index + self.ndescrpt * natoms[2+type_i] + dd = dd_all[:, start_index:end_index] + dd = np.reshape(dd, [-1, self.ndescrpt]) + start_index = end_index + # compute + dd = np.reshape (dd, [-1, 1]) + ddr = dd[:,:1] + sumr = np.sum(ddr) + sumn = dd.shape[0] + sumr2 = np.sum(np.multiply(ddr, ddr)) + sysr.append(sumr) + sysn.append(sumn) + sysr2.append(sumr2) + return sysr, sysr2, sysn + + + def _compute_std (self,sumv2, sumv, sumn) : + return np.sqrt(sumv2/sumn - np.multiply(sumv/sumn, sumv/sumn)) + + def _filter_r(self, + inputs, + natoms, + activation_fn=tf.nn.tanh, + stddev=1.0, + bavg=0.0, + name='linear', + reuse=None, + seed=None): + # natom x nei + shape = inputs.get_shape().as_list() + outputs_size = [1] + self.filter_neuron + with tf.variable_scope(name, reuse=reuse): + start_index = 0 + xyz_scatter_total = [] + for type_i in range(self.ntypes): + # cut-out inputs + # with natom x nei_type_i + inputs_i = tf.slice (inputs, + [ 0, start_index ], + [-1, self.sel_r[type_i]] ) + start_index += self.sel_r[type_i] + shape_i = inputs_i.get_shape().as_list() + # with (natom x nei_type_i) x 1 + xyz_scatter = tf.reshape(inputs_i, [-1, 1]) + for ii in range(1, len(outputs_size)): + w = tf.get_variable('matrix_'+str(ii)+'_'+str(type_i), + [outputs_size[ii - 1], outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev/np.sqrt(outputs_size[ii]+outputs_size[ii-1]), seed = seed)) + b = tf.get_variable('bias_'+str(ii)+'_'+str(type_i), + [1, outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev, mean = bavg, seed = seed)) + if self.filter_resnet_dt : + idt = tf.get_variable('idt_'+str(ii)+'_'+str(type_i), + [1, outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=0.001, mean = 1.0, seed = seed)) + if outputs_size[ii] == outputs_size[ii-1]: + if self.filter_resnet_dt : + xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) * idt + else : + xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) + elif outputs_size[ii] == outputs_size[ii-1] * 2: + if self.filter_resnet_dt : + xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) * idt + else : + xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) + else: + xyz_scatter = activation_fn(tf.matmul(xyz_scatter, w) + b) + # natom x nei_type_i x out_size + xyz_scatter = tf.reshape(xyz_scatter, (-1, shape_i[1], outputs_size[-1])) + xyz_scatter_total.append(xyz_scatter) + + # natom x nei x outputs_size + xyz_scatter = tf.concat(xyz_scatter_total, axis=1) + # natom x outputs_size + # + res_rescale = 1./5. + result = tf.reduce_mean(xyz_scatter, axis = 1) * res_rescale + + return result + diff --git a/source/train/Trainer.py b/source/train/Trainer.py index ec3bcba4cc..93372febba 100644 --- a/source/train/Trainer.py +++ b/source/train/Trainer.py @@ -12,6 +12,8 @@ from deepmd.RunOptions import global_cvt_2_tf_float from deepmd.RunOptions import global_cvt_2_ener_float from ModelSeA import ModelSeA +from ModelSeR import ModelSeR +from ModelHyb import ModelHyb from ModelLocFrame import ModelLocFrame from tensorflow.python.framework import ops @@ -28,6 +30,8 @@ import deepmd._prod_virial_grad import deepmd._prod_force_norot_grad import deepmd._prod_virial_norot_grad +import deepmd._prod_force_se_r_grad +import deepmd._prod_virial_se_r_grad import deepmd._soft_min_force_grad import deepmd._soft_min_virial_grad from deepmd.RunOptions import RunOptions @@ -75,13 +79,22 @@ def __init__(self, def _init_param(self, jdata): # descrpt config - self.use_smooth = False - if j_have (jdata, "use_smooth") : - self.use_smooth = jdata["use_smooth"] - if self.use_smooth: - self.model = ModelSeA(jdata) + model_type = j_must_have(jdata, 'model_type') + if model_type == 'loc_frame': + model_param = j_must_have(jdata, 'model') + self.model = ModelLocFrame(model_param) + elif model_type == 'se_a' : + model_param = j_must_have(jdata, 'model') + self.model = ModelSeA(model_param) + elif model_type == 'se_r' : + model_param = j_must_have(jdata, 'model') + self.model = ModelSeR(model_param) + elif model_type == 'se_ar' : + model_param_a = j_must_have(jdata, 'model_a') + model_param_r = j_must_have(jdata, 'model_r') + self.model = ModelHyb(model_param_a, model_param_r) else : - self.model = ModelLocFrame(jdata) + raise RuntimeError('unknow model type ' + model_type) self.numb_test = j_must_have (jdata, 'numb_test') self.useBN = False @@ -129,7 +142,7 @@ def build (self, data, lr) : self.lr = lr - self.ntypes = self.model.ntypes + self.ntypes = self.model.get_ntypes() assert (self.ntypes == data.get_ntypes()), "ntypes should match that found in data" self.batch_size = data.get_batch_size() @@ -186,7 +199,7 @@ def _build_network(self, davg, dstd, bias_atom_e): else : self.t_fparam = None - self.energy, self.force, self.virial, self.atom_ener \ + self.energy, self.force, self.virial, self.atom_ener, self.atom_virial\ = self.model.build_interaction (self.t_coord, self.t_type, self.t_natoms, diff --git a/source/train/train.py b/source/train/train.py index c8603b35e8..3815081cb9 100755 --- a/source/train/train.py +++ b/source/train/train.py @@ -80,23 +80,24 @@ def train (args) : _do_work(jdata, run_opt) def _do_work(jdata, run_opt): + # init the model + model = NNPTrainer (jdata, run_opt = run_opt) + rcut = model.model.get_rcut() # init params and run options systems = j_must_have(jdata, 'systems') set_pfx = j_must_have(jdata, 'set_prefix') numb_sys = len(systems) seed = None if 'seed' in jdata.keys() : seed = jdata['seed'] - seed = seed % (2**32) + if seed is not None: + seed = seed % (2**32) np.random.seed (seed) batch_size = j_must_have(jdata, 'batch_size') test_size = j_must_have(jdata, 'numb_test') stop_batch = j_must_have(jdata, 'stop_batch') - rcut = j_must_have (jdata, 'rcut') data = DataSystem(systems, set_pfx, batch_size, test_size, rcut, run_opt) tot_numb_batches = sum(data.get_nbatches()) lr = LearingRate (jdata, tot_numb_batches) - # init the model - model = NNPTrainer (jdata, run_opt = run_opt) # build the model with stats from the first system model.build (data, lr) # train the model with the provided systems in a cyclic way From 66c9758e1d66dbfb6edb34dff4fd139470a78a4c Mon Sep 17 00:00:00 2001 From: Linfeng Zhang Date: Sun, 23 Jun 2019 00:43:16 -0400 Subject: [PATCH 11/33] remove outdated assertion --- source/op/descrpt_se_r.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/source/op/descrpt_se_r.cc b/source/op/descrpt_se_r.cc index b0bbfed21e..04624d1e83 100644 --- a/source/op/descrpt_se_r.cc +++ b/source/op/descrpt_se_r.cc @@ -290,7 +290,6 @@ class DescrptSeROp : public OpKernel { rcut); // check sizes - assert (d_descrpt.size() == ndescrpt_a); assert (d_descrpt_deriv.size() == ndescrpt * 3); assert (d_rij.size() == nnei * 3); assert (int(fmt_nlist.size()) == nnei); From 389e6da307d3a1aff1aab763c8e0e5dab985543c Mon Sep 17 00:00:00 2001 From: Han Wang Date: Sun, 23 Jun 2019 12:48:18 +0800 Subject: [PATCH 12/33] virtual env should not use system packages --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e6d861c61..722d0ff8c9 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ A docker for installing the DeePMD-kit on CentOS 7 is available [here](https://g ### Install the Tensorflow's python interface We follow the virtual environment approach to install the tensorflow's Python interface. The full instruction can be found on [the tensorflow's official website](https://www.tensorflow.org/install/pip). Now we assume that the Python interface will be installed to virtual environment directory `$tensorflow_venv` ```bash -virtualenv --system-site-packages -p python3 $tensorflow_venv +virtualenv -p python3 $tensorflow_venv source $tensorflow_venv/bin/activate pip install --upgrade pip pip install --upgrade tensorflow==1.8.0 From d03583498e875ebbbc737df383f1df2d7efae9b3 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Thu, 27 Jun 2019 14:44:41 +0800 Subject: [PATCH 13/33] seperate DescrptSeA from ModelSeA --- source/train/CMakeLists.txt | 2 +- source/train/DescrptSeA.py | 447 ++++++++++++++++++++++++++++++++++++ source/train/Model.py | 5 +- source/train/ModelSeA.py | 385 ++----------------------------- 4 files changed, 472 insertions(+), 367 deletions(-) create mode 100644 source/train/DescrptSeA.py diff --git a/source/train/CMakeLists.txt b/source/train/CMakeLists.txt index 21471b76f4..d0f6cc4e4b 100644 --- a/source/train/CMakeLists.txt +++ b/source/train/CMakeLists.txt @@ -2,7 +2,7 @@ configure_file("RunOptions.py.in" "${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py" @ONLY) -file(GLOB LIB_PY common.py DeepPot.py Data.py DataSystem.py Model*.py Trainer.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) +file(GLOB LIB_PY common.py DeepPot.py Data.py DataSystem.py Model*.py Descrpt*.py Trainer.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) file(GLOB CLS_PY Local.py Slurm.py) diff --git a/source/train/DescrptSeA.py b/source/train/DescrptSeA.py new file mode 100644 index 0000000000..985df5e8d2 --- /dev/null +++ b/source/train/DescrptSeA.py @@ -0,0 +1,447 @@ +import os,warnings +import numpy as np +import tensorflow as tf +from deepmd.common import j_must_have, j_must_have_d, j_have +from deepmd.Model import Model + +from deepmd.RunOptions import global_tf_float_precision +from deepmd.RunOptions import global_np_float_precision +from deepmd.RunOptions import global_ener_float_precision +from deepmd.RunOptions import global_cvt_2_tf_float +from deepmd.RunOptions import global_cvt_2_ener_float + +module_path = os.path.dirname(os.path.realpath(__file__)) + "/" +assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" +op_module = tf.load_op_library(module_path + "libop_abi.so") + +class DescrptSeA (Model): + def __init__ (self, jdata): + # descrpt config + self.use_smooth = False + self.sel_a = j_must_have (jdata, 'sel_a') + self.sel_r = [ 0 for ii in range(len(self.sel_a)) ] + if j_have (jdata, 'sel_r') : + warnings.warn ('ignoring key sel_r in the json database and set sel_r to %s' % str(self.sel_r)) + self.ntypes = len(self.sel_a) + assert(self.ntypes == len(self.sel_r)) + self.rcut_a = -1 + self.rcut_r = j_must_have (jdata, 'rcut') + self.rcut = self.rcut_r + if j_have(jdata, 'rcut_smth') : + self.rcut_r_smth = jdata['rcut_smth'] + else : + self.rcut_r_smth = self.rcut_r + # fparam + self.numb_fparam = 0 + if j_have(jdata, 'numb_fparam') : + self.numb_fparam = jdata['numb_fparam'] + # type_map + self.type_map = [] + if j_have(jdata, 'type_map') : + self.numb_fparam = jdata['type_map'] + # filter of smooth version + if j_have(jdata, 'coord_norm') : + self.coord_norm = jdata['coord_norm'] + else : + self.coord_norm = True + self.filter_neuron = j_must_have (jdata, 'filter_neuron') + self.n_axis_neuron = j_must_have_d (jdata, 'axis_neuron', ['n_axis_neuron']) + self.filter_resnet_dt = False + if j_have(jdata, 'filter_resnet_dt') : + self.filter_resnet_dt = jdata['filter_resnet_dt'] + # numb of neighbors and numb of descrptors + self.nnei_a = np.cumsum(self.sel_a)[-1] + self.nnei_r = np.cumsum(self.sel_r)[-1] + self.nnei = self.nnei_a + self.nnei_r + self.ndescrpt_a = self.nnei_a * 4 + self.ndescrpt_r = self.nnei_r * 1 + self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r + + self.seed = None + if j_have (jdata, 'seed') : + self.seed = jdata['seed'] + self.useBN = False + + def get_rcut (self) : + return self.rcut + + def get_ntypes (self) : + return self.ntypes + + def get_type_map (self) : + return self.type_map + + def get_dim_out (self) : + return self.filter_neuron[-1] * self.n_axis_neuron + + def compute_dstats (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + all_davg = [] + all_dstd = [] + if True: + sumr = [] + suma = [] + sumn = [] + sumr2 = [] + suma2 = [] + for cc,bb,tt,nn,mm in zip(data_coord,data_box,data_atype,natoms_vec,mesh) : + sysr,sysr2,sysa,sysa2,sysn \ + = self._compute_dstats_sys_smth(cc,bb,tt,nn,mm,reuse) + sumr.append(sysr) + suma.append(sysa) + sumn.append(sysn) + sumr2.append(sysr2) + suma2.append(sysa2) + sumr = np.sum(sumr, axis = 0) + suma = np.sum(suma, axis = 0) + sumn = np.sum(sumn, axis = 0) + sumr2 = np.sum(sumr2, axis = 0) + suma2 = np.sum(suma2, axis = 0) + for type_i in range(self.ntypes) : + davgunit = [sumr[type_i]/sumn[type_i], 0, 0, 0] + dstdunit = [self._compute_std(sumr2[type_i], sumr[type_i], sumn[type_i]), + self._compute_std(suma2[type_i], suma[type_i], sumn[type_i]), + self._compute_std(suma2[type_i], suma[type_i], sumn[type_i]), + self._compute_std(suma2[type_i], suma[type_i], sumn[type_i]) + ] + davg = np.tile(davgunit, self.ndescrpt // 4) + dstd = np.tile(dstdunit, self.ndescrpt // 4) + all_davg.append(davg) + all_dstd.append(dstd) + + davg = np.array(all_davg) + dstd = np.array(all_dstd) + + return davg, dstd + + + def build (self, + coord_, + atype_, + natoms, + box, + mesh, + davg = None, + dstd = None, + suffix = '', + reuse_attr = None, + reuse_weights = None): + + with tf.variable_scope('model_attr' + suffix, reuse = reuse_attr) : + if davg is None: + davg = np.zeros([self.ntypes, self.ndescrpt]) + if dstd is None: + dstd = np.ones ([self.ntypes, self.ndescrpt]) + t_rcut = tf.constant(np.max([self.rcut_r, self.rcut_a]), + name = 'rcut', + dtype = global_tf_float_precision) + t_ntypes = tf.constant(self.ntypes, + name = 'ntypes', + dtype = tf.int32) + self.t_avg = tf.get_variable('t_avg', + davg.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(davg, dtype = global_tf_float_precision)) + self.t_std = tf.get_variable('t_std', + dstd.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(dstd, dtype = global_tf_float_precision)) + + coord = tf.reshape (coord_, [-1, natoms[1] * 3]) + atype = tf.reshape (atype_, [-1, natoms[1]]) + + self.descrpt, self.descrpt_deriv, self.rij, self.nlist \ + = op_module.descrpt_norot (coord, + atype, + natoms, + box, + mesh, + self.t_avg, + self.t_std, + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + rcut_r_smth = self.rcut_r_smth, + sel_a = self.sel_a, + sel_r = self.sel_r) + + self.dout = self._pass_filter(self.descrpt, natoms, suffix = suffix, reuse = reuse_weights) + + return self.dout + + + def prod_force_virial(self, atom_ener, natoms) : + [net_deriv] = tf.gradients (atom_ener, self.descrpt) + net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) + force \ + = op_module.prod_force_norot (net_deriv_reshape, + self.descrpt_deriv, + self.nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + virial, atom_virial \ + = op_module.prod_virial_norot (net_deriv_reshape, + self.descrpt_deriv, + self.rij, + self.nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + return force, virial, atom_virial + + + def _pass_filter(self, + inputs, + natoms, + reuse = None, + suffix = '') : + start_index = 0 + inputs = tf.reshape(inputs, [-1, self.ndescrpt * natoms[0]]) + shape = inputs.get_shape().as_list() + output = [] + for type_i in range(self.ntypes): + inputs_i = tf.slice (inputs, + [ 0, start_index* self.ndescrpt], + [-1, natoms[2+type_i]* self.ndescrpt] ) + inputs_i = tf.reshape(inputs_i, [-1, self.ndescrpt]) + layer = self._filter(inputs_i, name='filter_type_'+str(type_i)+suffix, natoms=natoms, reuse=reuse, seed = self.seed) + layer = tf.reshape(layer, [-1, natoms[2+type_i] * self.get_dim_out()]) + output.append(layer) + start_index += natoms[2+type_i] + output = tf.concat(output, axis = 1) + return output + + + def _compute_dstats_sys_smth (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + avg_zero = np.zeros([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) + std_ones = np.ones ([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) + sub_graph = tf.Graph() + with sub_graph.as_default(): + descrpt, descrpt_deriv, rij, nlist \ + = op_module.descrpt_norot (tf.constant(data_coord), + tf.constant(data_atype), + tf.constant(natoms_vec, dtype = tf.int32), + tf.constant(data_box), + tf.constant(mesh), + tf.constant(avg_zero), + tf.constant(std_ones), + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + rcut_r_smth = self.rcut_r_smth, + sel_a = self.sel_a, + sel_r = self.sel_r) + # self.sess.run(tf.global_variables_initializer()) + # sub_sess = tf.Session(graph = sub_graph, + # config=tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, + # inter_op_parallelism_threads=self.run_opt.num_inter_threads + + # )) + sub_sess = tf.Session(graph = sub_graph) + dd_all = sub_sess.run(descrpt) + sub_sess.close() + natoms = natoms_vec + dd_all = np.reshape(dd_all, [-1, self.ndescrpt * natoms[0]]) + start_index = 0 + sysr = [] + sysa = [] + sysn = [] + sysr2 = [] + sysa2 = [] + for type_i in range(self.ntypes): + end_index = start_index + self.ndescrpt * natoms[2+type_i] + dd = dd_all[:, start_index:end_index] + dd = np.reshape(dd, [-1, self.ndescrpt]) + start_index = end_index + # compute + dd = np.reshape (dd, [-1, 4]) + ddr = dd[:,:1] + dda = dd[:,1:] + sumr = np.sum(ddr) + suma = np.sum(dda) / 3. + sumn = dd.shape[0] + sumr2 = np.sum(np.multiply(ddr, ddr)) + suma2 = np.sum(np.multiply(dda, dda)) / 3. + sysr.append(sumr) + sysa.append(suma) + sysn.append(sumn) + sysr2.append(sumr2) + sysa2.append(suma2) + return sysr, sysr2, sysa, sysa2, sysn + + + def _compute_std (self,sumv2, sumv, sumn) : + return np.sqrt(sumv2/sumn - np.multiply(sumv/sumn, sumv/sumn)) + + + def _filter(self, + inputs, + natoms, + activation_fn=tf.nn.tanh, + stddev=1.0, + bavg=0.0, + name='linear', + reuse=None, + seed=None): + # natom x (nei x 4) + shape = inputs.get_shape().as_list() + outputs_size = [1] + self.filter_neuron + outputs_size_2 = self.n_axis_neuron + with tf.variable_scope(name, reuse=reuse): + start_index = 0 + xyz_scatter_total = [] + for type_i in range(self.ntypes): + # cut-out inputs + # with natom x (nei_type_i x 4) + inputs_i = tf.slice (inputs, + [ 0, start_index* 4], + [-1, self.sel_a[type_i]* 4] ) + start_index += self.sel_a[type_i] + shape_i = inputs_i.get_shape().as_list() + # with (natom x nei_type_i) x 4 + inputs_reshape = tf.reshape(inputs_i, [-1, 4]) + xyz_scatter = tf.reshape(tf.slice(inputs_reshape, [0,0],[-1,1]),[-1,1]) + for ii in range(1, len(outputs_size)): + w = tf.get_variable('matrix_'+str(ii)+'_'+str(type_i), + [outputs_size[ii - 1], outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev/np.sqrt(outputs_size[ii]+outputs_size[ii-1]), seed = seed)) + b = tf.get_variable('bias_'+str(ii)+'_'+str(type_i), + [1, outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev, mean = bavg, seed = seed)) + if self.filter_resnet_dt : + idt = tf.get_variable('idt_'+str(ii)+'_'+str(type_i), + [1, outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=0.001, mean = 1.0, seed = seed)) + if outputs_size[ii] == outputs_size[ii-1]: + if self.filter_resnet_dt : + xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) * idt + else : + xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) + elif outputs_size[ii] == outputs_size[ii-1] * 2: + if self.filter_resnet_dt : + xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) * idt + else : + xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) + else: + xyz_scatter = activation_fn(tf.matmul(xyz_scatter, w) + b) + # natom x nei_type_i x out_size + xyz_scatter = tf.reshape(xyz_scatter, (-1, shape_i[1]//4, outputs_size[-1])) + xyz_scatter_total.append(xyz_scatter) + + # natom x nei x outputs_size + xyz_scatter = tf.concat(xyz_scatter_total, axis=1) + # natom x nei x 4 + inputs_reshape = tf.reshape(inputs, [-1, shape[1]//4, 4]) + # natom x 4 x outputs_size + xyz_scatter_1 = tf.matmul(inputs_reshape, xyz_scatter, transpose_a = True) + xyz_scatter_1 = xyz_scatter_1 * (4.0 / shape[1]) + # natom x 4 x outputs_size_2 + xyz_scatter_2 = tf.slice(xyz_scatter_1, [0,0,0],[-1,-1,outputs_size_2]) + # natom x outputs_size x outputs_size_2 + result = tf.matmul(xyz_scatter_1, xyz_scatter_2, transpose_a = True) + # natom x (outputs_size x outputs_size_2) + result = tf.reshape(result, [-1, outputs_size_2 * outputs_size[-1]]) + + return result + + def _filter_type_ext(self, + inputs, + natoms, + activation_fn=tf.nn.tanh, + stddev=1.0, + bavg=0.0, + name='linear', + reuse=None, + seed=None): + # natom x (nei x 4) + shape = inputs.get_shape().as_list() + outputs_size = [1] + self.filter_neuron + outputs_size_2 = self.n_axis_neuron + with tf.variable_scope(name, reuse=reuse): + start_index = 0 + result_all = [] + xyz_scatter_1_all = [] + xyz_scatter_2_all = [] + for type_i in range(self.ntypes): + # cut-out inputs + # with natom x (nei_type_i x 4) + inputs_i = tf.slice (inputs, + [ 0, start_index* 4], + [-1, self.sel_a[type_i]* 4] ) + start_index += self.sel_a[type_i] + shape_i = inputs_i.get_shape().as_list() + # with (natom x nei_type_i) x 4 + inputs_reshape = tf.reshape(inputs_i, [-1, 4]) + xyz_scatter = tf.reshape(tf.slice(inputs_reshape, [0,0],[-1,1]),[-1,1]) + for ii in range(1, len(outputs_size)): + w = tf.get_variable('matrix_'+str(ii)+'_'+str(type_i), + [outputs_size[ii - 1], outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev/np.sqrt(outputs_size[ii]+outputs_size[ii-1]), seed = seed)) + b = tf.get_variable('bias_'+str(ii)+'_'+str(type_i), + [1, outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev, mean = bavg, seed = seed)) + if self.filter_resnet_dt : + idt = tf.get_variable('idt_'+str(ii)+'_'+str(type_i), + [1, outputs_size[ii]], + global_tf_float_precision, + tf.random_normal_initializer(stddev=0.001, mean = 1.0, seed = seed)) + if outputs_size[ii] == outputs_size[ii-1]: + if self.filter_resnet_dt : + xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) * idt + else : + xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) + elif outputs_size[ii] == outputs_size[ii-1] * 2: + if self.filter_resnet_dt : + xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) * idt + else : + xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) + else: + xyz_scatter = activation_fn(tf.matmul(xyz_scatter, w) + b) + # natom x nei_type_i x out_size + xyz_scatter = tf.reshape(xyz_scatter, (-1, shape_i[1]//4, outputs_size[-1])) + # natom x nei_type_i x 4 + inputs_i_reshape = tf.reshape(inputs_i, [-1, shape_i[1]//4, 4]) + # natom x 4 x outputs_size + xyz_scatter_1 = tf.matmul(inputs_i_reshape, xyz_scatter, transpose_a = True) + xyz_scatter_1 = xyz_scatter_1 * (4.0 / shape_i[1]) + # natom x 4 x outputs_size_2 + xyz_scatter_2 = tf.slice(xyz_scatter_1, [0,0,0],[-1,-1,outputs_size_2]) + xyz_scatter_1_all.append(xyz_scatter_1) + xyz_scatter_2_all.append(xyz_scatter_2) + + # for type_i in range(self.ntypes): + # for type_j in range(type_i, self.ntypes): + # # natom x outputs_size x outputs_size_2 + # result = tf.matmul(xyz_scatter_1_all[type_i], xyz_scatter_2_all[type_j], transpose_a = True) + # # natom x (outputs_size x outputs_size_2) + # result = tf.reshape(result, [-1, outputs_size_2 * outputs_size[-1]]) + # result_all.append(tf.identity(result)) + xyz_scatter_2_coll = tf.concat(xyz_scatter_2_all, axis = 2) + for type_i in range(self.ntypes) : + # natom x outputs_size x (outputs_size_2 x ntypes) + result = tf.matmul(xyz_scatter_1_all[type_i], xyz_scatter_2_coll, transpose_a = True) + # natom x (outputs_size x outputs_size_2 x ntypes) + result = tf.reshape(result, [-1, outputs_size_2 * self.ntypes * outputs_size[-1]]) + result_all.append(tf.identity(result)) + + # natom x (ntypes x outputs_size x outputs_size_2 x ntypes) + result_all = tf.concat(result_all, axis = 1) + + return result_all diff --git a/source/train/Model.py b/source/train/Model.py index 72e45c6960..3ed8a30dd5 100644 --- a/source/train/Model.py +++ b/source/train/Model.py @@ -26,10 +26,7 @@ def data_stat(self, data): all_natoms_vec.append(natoms_vec) all_default_mesh.append(default_mesh) - if not self.coord_norm : - davg, dstd = self.no_norm_dstats () - else : - davg, dstd = self.compute_dstats (all_stat_coord, all_stat_box, all_stat_type, all_natoms_vec, all_default_mesh) + davg, dstd = self.compute_dstats (all_stat_coord, all_stat_box, all_stat_type, all_natoms_vec, all_default_mesh) # if self.run_opt.is_chief: # np.savetxt ("stat.avg.out", davg.T) # np.savetxt ("stat.std.out", dstd.T) diff --git a/source/train/ModelSeA.py b/source/train/ModelSeA.py index bfc57291c0..e7f8f392f6 100644 --- a/source/train/ModelSeA.py +++ b/source/train/ModelSeA.py @@ -3,6 +3,7 @@ import tensorflow as tf from deepmd.common import j_must_have, j_must_have_d, j_have from deepmd.Model import Model +from deepmd.DescrptSeA import DescrptSeA from deepmd.RunOptions import global_tf_float_precision from deepmd.RunOptions import global_np_float_precision @@ -16,21 +17,8 @@ class ModelSeA (Model): def __init__ (self, jdata): - # descrpt config - self.use_smooth = False - self.sel_a = j_must_have (jdata, 'sel_a') - self.sel_r = [ 0 for ii in range(len(self.sel_a)) ] - if j_have (jdata, 'sel_r') : - warnings.warn ('ignoring key sel_r in the json database and set sel_r to %s' % str(self.sel_r)) - self.ntypes = len(self.sel_a) - assert(self.ntypes == len(self.sel_r)) - self.rcut_a = -1 - self.rcut_r = j_must_have (jdata, 'rcut') - self.rcut = self.rcut_r - if j_have(jdata, 'rcut_smth') : - self.rcut_r_smth = jdata['rcut_smth'] - else : - self.rcut_r_smth = self.rcut_r + self.descrpt = DescrptSeA(jdata) + self.ntypes = self.descrpt.get_ntypes() # fparam self.numb_fparam = 0 if j_have(jdata, 'numb_fparam') : @@ -39,23 +27,6 @@ def __init__ (self, jdata): self.type_map = [] if j_have(jdata, 'type_map') : self.numb_fparam = jdata['type_map'] - # filter of smooth version - if j_have(jdata, 'coord_norm') : - self.coord_norm = jdata['coord_norm'] - else : - self.coord_norm = True - self.filter_neuron = j_must_have (jdata, 'filter_neuron') - self.n_axis_neuron = j_must_have_d (jdata, 'axis_neuron', ['n_axis_neuron']) - self.filter_resnet_dt = False - if j_have(jdata, 'filter_resnet_dt') : - self.filter_resnet_dt = jdata['filter_resnet_dt'] - # numb of neighbors and numb of descrptors - self.nnei_a = np.cumsum(self.sel_a)[-1] - self.nnei_r = np.cumsum(self.sel_r)[-1] - self.nnei = self.nnei_a + self.nnei_r - self.ndescrpt_a = self.nnei_a * 4 - self.ndescrpt_r = self.nnei_r * 1 - self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r # network size self.n_neuron = j_must_have_d (jdata, 'fitting_neuron', ['n_neuron']) self.resnet_dt = True @@ -85,7 +56,7 @@ def __init__ (self, jdata): def get_rcut (self) : - return self.rcut + return self.descrpt.get_rcut() def get_ntypes (self) : return self.ntypes @@ -102,45 +73,9 @@ def compute_dstats (self, data_atype, natoms_vec, mesh, - reuse = None) : - all_davg = [] - all_dstd = [] - if True: - sumr = [] - suma = [] - sumn = [] - sumr2 = [] - suma2 = [] - for cc,bb,tt,nn,mm in zip(data_coord,data_box,data_atype,natoms_vec,mesh) : - sysr,sysr2,sysa,sysa2,sysn \ - = self._compute_dstats_sys_smth(cc,bb,tt,nn,mm,reuse) - sumr.append(sysr) - suma.append(sysa) - sumn.append(sysn) - sumr2.append(sysr2) - suma2.append(sysa2) - sumr = np.sum(sumr, axis = 0) - suma = np.sum(suma, axis = 0) - sumn = np.sum(sumn, axis = 0) - sumr2 = np.sum(sumr2, axis = 0) - suma2 = np.sum(suma2, axis = 0) - for type_i in range(self.ntypes) : - davgunit = [sumr[type_i]/sumn[type_i], 0, 0, 0] - dstdunit = [self._compute_std(sumr2[type_i], sumr[type_i], sumn[type_i]), - self._compute_std(suma2[type_i], suma[type_i], sumn[type_i]), - self._compute_std(suma2[type_i], suma[type_i], sumn[type_i]), - self._compute_std(suma2[type_i], suma[type_i], sumn[type_i]) - ] - davg = np.tile(davgunit, self.ndescrpt // 4) - dstd = np.tile(dstdunit, self.ndescrpt // 4) - all_davg.append(davg) - all_dstd.append(dstd) - - davg = np.array(all_davg) - dstd = np.array(all_dstd) - - return davg, dstd - + reuse = None) : + return self.descrpt.compute_dstats(data_coord, data_box, data_atype, natoms_vec, mesh, reuse) + def build_interaction (self, coord_, @@ -155,33 +90,15 @@ def build_interaction (self, suffix = '', reuse_attr = None, reuse_weights = None): + with tf.variable_scope('model_attr' + suffix, reuse = reuse_attr) : - if davg is None: - davg = np.zeros([self.ntypes, self.ndescrpt]) - if dstd is None: - dstd = np.ones ([self.ntypes, self.ndescrpt]) - t_rcut = tf.constant(np.max([self.rcut_r, self.rcut_a]), - name = 'rcut', - dtype = global_tf_float_precision) - t_ntypes = tf.constant(self.ntypes, - name = 'ntypes', - dtype = tf.int32) t_dfparam = tf.constant(self.numb_fparam, name = 'dfparam', dtype = tf.int32) t_tmap = tf.constant(' '.join(self.type_map), name = 'tmap', dtype = tf.string) - self.t_avg = tf.get_variable('t_avg', - davg.shape, - dtype = global_tf_float_precision, - trainable = False, - initializer = tf.constant_initializer(davg, dtype = global_tf_float_precision)) - self.t_std = tf.get_variable('t_std', - dstd.shape, - dtype = global_tf_float_precision, - trainable = False, - initializer = tf.constant_initializer(dstd, dtype = global_tf_float_precision)) + if self.srtab is not None : tab_info, tab_data = self.srtab.get() self.tab_info = tf.get_variable('t_tab_info', @@ -198,23 +115,9 @@ def build_interaction (self, coord = tf.reshape (coord_, [-1, natoms[1] * 3]) atype = tf.reshape (atype_, [-1, natoms[1]]) - descrpt, descrpt_deriv, rij, nlist \ - = op_module.descrpt_norot (coord, - atype, - natoms, - box, - mesh, - self.t_avg, - self.t_std, - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - rcut_r_smth = self.rcut_r_smth, - sel_a = self.sel_a, - sel_r = self.sel_r) - - descrpt_reshape = tf.reshape(descrpt, [-1, self.ndescrpt]) - - atom_ener = self.build_atom_net (descrpt_reshape, + descrpt = self.descrpt.build(coord_, atype_, natoms, box, mesh, davg = davg, dstd = dstd, suffix = suffix, reuse_attr = reuse_attr, reuse_weights = reuse_weights) + + atom_ener = self.build_atom_net (descrpt, fparam, natoms, bias_atom_e = bias_atom_e, @@ -256,16 +159,9 @@ def build_interaction (self, energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'o_atom_energy'+suffix) energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='o_energy'+suffix) - net_deriv_tmp = tf.gradients (atom_ener, descrpt_reshape) - net_deriv = net_deriv_tmp[0] - net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) + force, virial, atom_virial \ + = self.descrpt.prod_force_virial (atom_ener, natoms) - force = op_module.prod_force_norot (net_deriv_reshape, - descrpt_deriv, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) if self.srtab is not None : sw_force \ = op_module.soft_min_force(energy_diff, @@ -278,14 +174,6 @@ def build_interaction (self, force = tf.reshape (force, [-1, 3 * natoms[1]], name = "o_force"+suffix) - virial, atom_virial \ - = op_module.prod_virial_norot (net_deriv_reshape, - descrpt_deriv, - rij, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) if self.srtab is not None : sw_virial, sw_atom_virial \ = op_module.soft_min_virial (energy_diff, @@ -305,7 +193,6 @@ def build_interaction (self, return energy, force, virial, energy_raw, atom_virial - def build_atom_net (self, inputs, fparam, @@ -314,7 +201,7 @@ def build_atom_net (self, reuse = None, suffix = '') : start_index = 0 - inputs = tf.reshape(inputs, [-1, self.ndescrpt * natoms[0]]) + inputs = tf.reshape(inputs, [-1, self.descrpt.get_dim_out() * natoms[0]]) shape = inputs.get_shape().as_list() if bias_atom_e is not None : assert(len(bias_atom_e) == self.ntypes) @@ -322,20 +209,16 @@ def build_atom_net (self, for type_i in range(self.ntypes): # cut-out inputs inputs_i = tf.slice (inputs, - [ 0, start_index* self.ndescrpt], - [-1, natoms[2+type_i]* self.ndescrpt] ) - inputs_i = tf.reshape(inputs_i, [-1, self.ndescrpt]) + [ 0, start_index* self.descrpt.get_dim_out()], + [-1, natoms[2+type_i]* self.descrpt.get_dim_out()] ) + inputs_i = tf.reshape(inputs_i, [-1, self.descrpt.get_dim_out()]) start_index += natoms[2+type_i] if bias_atom_e is None : type_bias_ae = 0.0 else : type_bias_ae = bias_atom_e[type_i] - # compute atom energy - if self.type_fitting_net : - layer = self._filter_type_ext(inputs_i, name='filter_type_'+str(type_i)+suffix, natoms=natoms, reuse=reuse, seed = self.seed) - else : - layer = self._filter(inputs_i, name='filter_type_'+str(type_i)+suffix, natoms=natoms, reuse=reuse, seed = self.seed) + layer = inputs_i if self.numb_fparam > 0 : ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) @@ -359,229 +242,7 @@ def build_atom_net (self, return tf.reshape(outs, [-1]) - def _compute_dstats_sys_smth (self, - data_coord, - data_box, - data_atype, - natoms_vec, - mesh, - reuse = None) : - avg_zero = np.zeros([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) - std_ones = np.ones ([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) - sub_graph = tf.Graph() - with sub_graph.as_default(): - descrpt, descrpt_deriv, rij, nlist \ - = op_module.descrpt_norot (tf.constant(data_coord), - tf.constant(data_atype), - tf.constant(natoms_vec, dtype = tf.int32), - tf.constant(data_box), - tf.constant(mesh), - tf.constant(avg_zero), - tf.constant(std_ones), - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - rcut_r_smth = self.rcut_r_smth, - sel_a = self.sel_a, - sel_r = self.sel_r) - # self.sess.run(tf.global_variables_initializer()) - # sub_sess = tf.Session(graph = sub_graph, - # config=tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, - # inter_op_parallelism_threads=self.run_opt.num_inter_threads - - # )) - sub_sess = tf.Session(graph = sub_graph) - dd_all = sub_sess.run(descrpt) - sub_sess.close() - natoms = natoms_vec - dd_all = np.reshape(dd_all, [-1, self.ndescrpt * natoms[0]]) - start_index = 0 - sysr = [] - sysa = [] - sysn = [] - sysr2 = [] - sysa2 = [] - for type_i in range(self.ntypes): - end_index = start_index + self.ndescrpt * natoms[2+type_i] - dd = dd_all[:, start_index:end_index] - dd = np.reshape(dd, [-1, self.ndescrpt]) - start_index = end_index - # compute - dd = np.reshape (dd, [-1, 4]) - ddr = dd[:,:1] - dda = dd[:,1:] - sumr = np.sum(ddr) - suma = np.sum(dda) / 3. - sumn = dd.shape[0] - sumr2 = np.sum(np.multiply(ddr, ddr)) - suma2 = np.sum(np.multiply(dda, dda)) / 3. - sysr.append(sumr) - sysa.append(suma) - sysn.append(sumn) - sysr2.append(sumr2) - sysa2.append(suma2) - return sysr, sysr2, sysa, sysa2, sysn - - - def _compute_std (self,sumv2, sumv, sumn) : - return np.sqrt(sumv2/sumn - np.multiply(sumv/sumn, sumv/sumn)) - - - def _filter(self, - inputs, - natoms, - activation_fn=tf.nn.tanh, - stddev=1.0, - bavg=0.0, - name='linear', - reuse=None, - seed=None): - # natom x (nei x 4) - shape = inputs.get_shape().as_list() - outputs_size = [1] + self.filter_neuron - outputs_size_2 = self.n_axis_neuron - with tf.variable_scope(name, reuse=reuse): - start_index = 0 - xyz_scatter_total = [] - for type_i in range(self.ntypes): - # cut-out inputs - # with natom x (nei_type_i x 4) - inputs_i = tf.slice (inputs, - [ 0, start_index* 4], - [-1, self.sel_a[type_i]* 4] ) - start_index += self.sel_a[type_i] - shape_i = inputs_i.get_shape().as_list() - # with (natom x nei_type_i) x 4 - inputs_reshape = tf.reshape(inputs_i, [-1, 4]) - xyz_scatter = tf.reshape(tf.slice(inputs_reshape, [0,0],[-1,1]),[-1,1]) - for ii in range(1, len(outputs_size)): - w = tf.get_variable('matrix_'+str(ii)+'_'+str(type_i), - [outputs_size[ii - 1], outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=stddev/np.sqrt(outputs_size[ii]+outputs_size[ii-1]), seed = seed)) - b = tf.get_variable('bias_'+str(ii)+'_'+str(type_i), - [1, outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=stddev, mean = bavg, seed = seed)) - if self.filter_resnet_dt : - idt = tf.get_variable('idt_'+str(ii)+'_'+str(type_i), - [1, outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=0.001, mean = 1.0, seed = seed)) - if outputs_size[ii] == outputs_size[ii-1]: - if self.filter_resnet_dt : - xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) * idt - else : - xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) - elif outputs_size[ii] == outputs_size[ii-1] * 2: - if self.filter_resnet_dt : - xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) * idt - else : - xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) - else: - xyz_scatter = activation_fn(tf.matmul(xyz_scatter, w) + b) - # natom x nei_type_i x out_size - xyz_scatter = tf.reshape(xyz_scatter, (-1, shape_i[1]//4, outputs_size[-1])) - xyz_scatter_total.append(xyz_scatter) - - # natom x nei x outputs_size - xyz_scatter = tf.concat(xyz_scatter_total, axis=1) - # natom x nei x 4 - inputs_reshape = tf.reshape(inputs, [-1, shape[1]//4, 4]) - # natom x 4 x outputs_size - xyz_scatter_1 = tf.matmul(inputs_reshape, xyz_scatter, transpose_a = True) - xyz_scatter_1 = xyz_scatter_1 * (4.0 / shape[1]) - # natom x 4 x outputs_size_2 - xyz_scatter_2 = tf.slice(xyz_scatter_1, [0,0,0],[-1,-1,outputs_size_2]) - # natom x outputs_size x outputs_size_2 - result = tf.matmul(xyz_scatter_1, xyz_scatter_2, transpose_a = True) - # natom x (outputs_size x outputs_size_2) - result = tf.reshape(result, [-1, outputs_size_2 * outputs_size[-1]]) - - return result - - def _filter_type_ext(self, - inputs, - natoms, - activation_fn=tf.nn.tanh, - stddev=1.0, - bavg=0.0, - name='linear', - reuse=None, - seed=None): - # natom x (nei x 4) - shape = inputs.get_shape().as_list() - outputs_size = [1] + self.filter_neuron - outputs_size_2 = self.n_axis_neuron - with tf.variable_scope(name, reuse=reuse): - start_index = 0 - result_all = [] - xyz_scatter_1_all = [] - xyz_scatter_2_all = [] - for type_i in range(self.ntypes): - # cut-out inputs - # with natom x (nei_type_i x 4) - inputs_i = tf.slice (inputs, - [ 0, start_index* 4], - [-1, self.sel_a[type_i]* 4] ) - start_index += self.sel_a[type_i] - shape_i = inputs_i.get_shape().as_list() - # with (natom x nei_type_i) x 4 - inputs_reshape = tf.reshape(inputs_i, [-1, 4]) - xyz_scatter = tf.reshape(tf.slice(inputs_reshape, [0,0],[-1,1]),[-1,1]) - for ii in range(1, len(outputs_size)): - w = tf.get_variable('matrix_'+str(ii)+'_'+str(type_i), - [outputs_size[ii - 1], outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=stddev/np.sqrt(outputs_size[ii]+outputs_size[ii-1]), seed = seed)) - b = tf.get_variable('bias_'+str(ii)+'_'+str(type_i), - [1, outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=stddev, mean = bavg, seed = seed)) - if self.filter_resnet_dt : - idt = tf.get_variable('idt_'+str(ii)+'_'+str(type_i), - [1, outputs_size[ii]], - global_tf_float_precision, - tf.random_normal_initializer(stddev=0.001, mean = 1.0, seed = seed)) - if outputs_size[ii] == outputs_size[ii-1]: - if self.filter_resnet_dt : - xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) * idt - else : - xyz_scatter += activation_fn(tf.matmul(xyz_scatter, w) + b) - elif outputs_size[ii] == outputs_size[ii-1] * 2: - if self.filter_resnet_dt : - xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) * idt - else : - xyz_scatter = tf.concat([xyz_scatter,xyz_scatter], 1) + activation_fn(tf.matmul(xyz_scatter, w) + b) - else: - xyz_scatter = activation_fn(tf.matmul(xyz_scatter, w) + b) - # natom x nei_type_i x out_size - xyz_scatter = tf.reshape(xyz_scatter, (-1, shape_i[1]//4, outputs_size[-1])) - # natom x nei_type_i x 4 - inputs_i_reshape = tf.reshape(inputs_i, [-1, shape_i[1]//4, 4]) - # natom x 4 x outputs_size - xyz_scatter_1 = tf.matmul(inputs_i_reshape, xyz_scatter, transpose_a = True) - xyz_scatter_1 = xyz_scatter_1 * (4.0 / shape_i[1]) - # natom x 4 x outputs_size_2 - xyz_scatter_2 = tf.slice(xyz_scatter_1, [0,0,0],[-1,-1,outputs_size_2]) - xyz_scatter_1_all.append(xyz_scatter_1) - xyz_scatter_2_all.append(xyz_scatter_2) - - # for type_i in range(self.ntypes): - # for type_j in range(type_i, self.ntypes): - # # natom x outputs_size x outputs_size_2 - # result = tf.matmul(xyz_scatter_1_all[type_i], xyz_scatter_2_all[type_j], transpose_a = True) - # # natom x (outputs_size x outputs_size_2) - # result = tf.reshape(result, [-1, outputs_size_2 * outputs_size[-1]]) - # result_all.append(tf.identity(result)) - xyz_scatter_2_coll = tf.concat(xyz_scatter_2_all, axis = 2) - for type_i in range(self.ntypes) : - # natom x outputs_size x (outputs_size_2 x ntypes) - result = tf.matmul(xyz_scatter_1_all[type_i], xyz_scatter_2_coll, transpose_a = True) - # natom x (outputs_size x outputs_size_2 x ntypes) - result = tf.reshape(result, [-1, outputs_size_2 * self.ntypes * outputs_size[-1]]) - result_all.append(tf.identity(result)) - - # natom x (ntypes x outputs_size x outputs_size_2 x ntypes) - result_all = tf.concat(result_all, axis = 1) - - return result_all + + + + From b80fe6696002f150cb6ef26779b9da705fb97b1b Mon Sep 17 00:00:00 2001 From: Han Wang Date: Thu, 27 Jun 2019 16:23:38 +0800 Subject: [PATCH 14/33] seperate descrptors and energy fitting from model --- .gitignore | 2 + source/train/CMakeLists.txt | 2 +- source/train/DescrptLocFrame.py | 211 +++++++++++ source/train/DescrptSeA.py | 13 +- source/train/{ModelSeR.py => DescrptSeR.py} | 237 +++--------- source/train/EnerFitting.py | 133 +++++++ source/train/Model.py | 215 ++++++++--- source/train/ModelLocFrame.py | 380 -------------------- source/train/ModelSeA.py | 248 ------------- source/train/Trainer.py | 33 +- 10 files changed, 587 insertions(+), 887 deletions(-) create mode 100644 source/train/DescrptLocFrame.py rename source/train/{ModelSeR.py => DescrptSeR.py} (55%) create mode 100644 source/train/EnerFitting.py delete mode 100644 source/train/ModelLocFrame.py delete mode 100644 source/train/ModelSeA.py diff --git a/.gitignore b/.gitignore index d62d5d5a93..24a94d7f9d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ *.pyc CMakeCache.txt CMakeFiles +*.bk + diff --git a/source/train/CMakeLists.txt b/source/train/CMakeLists.txt index d0f6cc4e4b..1455ae04cc 100644 --- a/source/train/CMakeLists.txt +++ b/source/train/CMakeLists.txt @@ -2,7 +2,7 @@ configure_file("RunOptions.py.in" "${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py" @ONLY) -file(GLOB LIB_PY common.py DeepPot.py Data.py DataSystem.py Model*.py Descrpt*.py Trainer.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) +file(GLOB LIB_PY common.py DeepPot.py Data.py DataSystem.py Model*.py Descrpt*.py EnerFitting.py Trainer.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) file(GLOB CLS_PY Local.py Slurm.py) diff --git a/source/train/DescrptLocFrame.py b/source/train/DescrptLocFrame.py new file mode 100644 index 0000000000..feb148a587 --- /dev/null +++ b/source/train/DescrptLocFrame.py @@ -0,0 +1,211 @@ +import os +import numpy as np +import tensorflow as tf +from deepmd.common import j_must_have, j_must_have_d, j_have + +from deepmd.RunOptions import global_tf_float_precision +from deepmd.RunOptions import global_np_float_precision +from deepmd.RunOptions import global_ener_float_precision +from deepmd.RunOptions import global_cvt_2_tf_float +from deepmd.RunOptions import global_cvt_2_ener_float + +module_path = os.path.dirname(os.path.realpath(__file__)) + "/" +assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" +op_module = tf.load_op_library(module_path + "libop_abi.so") + + +class DescrptLocFrame () : + def __init__(self, jdata): + # descrpt config + self.sel_a = j_must_have (jdata, 'sel_a') + self.sel_r = j_must_have (jdata, 'sel_r') + self.ntypes = len(self.sel_a) + assert(self.ntypes == len(self.sel_r)) + self.rcut_a = -1 + self.rcut_r = j_must_have (jdata, 'rcut') + # axis + self.axis_rule = j_must_have (jdata, 'axis_rule') + # numb of neighbors and numb of descrptors + self.nnei_a = np.cumsum(self.sel_a)[-1] + self.nnei_r = np.cumsum(self.sel_r)[-1] + self.nnei = self.nnei_a + self.nnei_r + self.ndescrpt_a = self.nnei_a * 4 + self.ndescrpt_r = self.nnei_r * 1 + self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r + + def get_rcut (self) : + return self.rcut_r + + def get_ntypes (self) : + return self.ntypes + + def get_dim_out (self) : + return self.ndescrpt + + def compute_dstats (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + all_davg = [] + all_dstd = [] + if True: + sumv = [] + sumn = [] + sumv2 = [] + for cc,bb,tt,nn,mm in zip(data_coord,data_box,data_atype,natoms_vec,mesh) : + sysv,sysv2,sysn \ + = self._compute_dstats_sys_nonsmth(cc,bb,tt,nn,mm,reuse) + sumv.append(sysv) + sumn.append(sysn) + sumv2.append(sysv2) + sumv = np.sum(sumv, axis = 0) + sumn = np.sum(sumn, axis = 0) + sumv2 = np.sum(sumv2, axis = 0) + for type_i in range(self.ntypes) : + davg = sumv[type_i] / sumn[type_i] + dstd = self._compute_std(sumv2[type_i], sumv[type_i], sumn[type_i]) + for ii in range (len(dstd)) : + if (np.abs(dstd[ii]) < 1e-2) : + dstd[ii] = 1e-2 + all_davg.append(davg) + all_dstd.append(dstd) + davg = np.array(all_davg) + dstd = np.array(all_dstd) + return davg, dstd + + + def build (self, + coord_, + atype_, + natoms, + box, + mesh, + davg = None, + dstd = None, + suffix = '', + reuse = None): + with tf.variable_scope('model_attr' + suffix, reuse = reuse) : + if davg is None: + davg = np.zeros([self.ntypes, self.ndescrpt]) + if dstd is None: + dstd = np.ones ([self.ntypes, self.ndescrpt]) + t_rcut = tf.constant(np.max([self.rcut_r, self.rcut_a]), + name = 'rcut', + dtype = global_tf_float_precision) + t_ntypes = tf.constant(self.ntypes, + name = 'ntypes', + dtype = tf.int32) + self.t_avg = tf.get_variable('t_avg', + davg.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(davg, dtype = global_tf_float_precision)) + self.t_std = tf.get_variable('t_std', + dstd.shape, + dtype = global_tf_float_precision, + trainable = False, + initializer = tf.constant_initializer(dstd, dtype = global_tf_float_precision)) + + coord = tf.reshape (coord_, [-1, natoms[1] * 3]) + atype = tf.reshape (atype_, [-1, natoms[1]]) + + self.descrpt, self.descrpt_deriv, self.rij, self.nlist, self.axis \ + = op_module.descrpt (coord, + atype, + natoms, + box, + mesh, + self.t_avg, + self.t_std, + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + sel_a = self.sel_a, + sel_r = self.sel_r, + axis_rule = self.axis_rule) + self.descrpt = tf.reshape(self.descrpt, [-1, self.ndescrpt]) + return self.descrpt + + + def prod_force_virial(self, atom_ener, natoms) : + [net_deriv] = tf.gradients (atom_ener, self.descrpt) + net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) + force = op_module.prod_force (net_deriv_reshape, + self.descrpt_deriv, + self.nlist, + self.axis, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + virial, atom_virial \ + = op_module.prod_virial (net_deriv_reshape, + self.descrpt_deriv, + self.rij, + self.nlist, + self.axis, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + + return force, virial, atom_virial + + + def _compute_dstats_sys_nonsmth (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + avg_zero = np.zeros([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) + std_ones = np.ones ([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) + sub_graph = tf.Graph() + with sub_graph.as_default(): + descrpt, descrpt_deriv, rij, nlist, axis \ + = op_module.descrpt (tf.constant(data_coord), + tf.constant(data_atype), + tf.constant(natoms_vec, dtype = tf.int32), + tf.constant(data_box), + tf.constant(mesh), + tf.constant(avg_zero), + tf.constant(std_ones), + rcut_a = self.rcut_a, + rcut_r = self.rcut_r, + sel_a = self.sel_a, + sel_r = self.sel_r, + axis_rule = self.axis_rule) + # self.sess.run(tf.global_variables_initializer()) + # sub_sess = tf.Session(graph = sub_graph, + # config=tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, + # inter_op_parallelism_threads=self.run_opt.num_inter_threads + # )) + sub_sess = tf.Session(graph = sub_graph) + dd_all = sub_sess.run(descrpt) + sub_sess.close() + natoms = natoms_vec + dd_all = np.reshape(dd_all, [-1, self.ndescrpt * natoms[0]]) + start_index = 0 + sysv = [] + sysn = [] + sysv2 = [] + for type_i in range(self.ntypes): + end_index = start_index + self.ndescrpt * natoms[2+type_i] + dd = dd_all[:, start_index:end_index] + dd = np.reshape(dd, [-1, self.ndescrpt]) + start_index = end_index + # compute + sumv = np.sum(dd, axis = 0) + sumn = dd.shape[0] + sumv2 = np.sum(np.multiply(dd,dd), axis = 0) + sysv.append(sumv) + sysn.append(sumn) + sysv2.append(sumv2) + return sysv, sysv2, sysn + + + def _compute_std (self,sumv2, sumv, sumn) : + return np.sqrt(sumv2/sumn - np.multiply(sumv/sumn, sumv/sumn)) + + diff --git a/source/train/DescrptSeA.py b/source/train/DescrptSeA.py index 985df5e8d2..b86018b4ba 100644 --- a/source/train/DescrptSeA.py +++ b/source/train/DescrptSeA.py @@ -2,7 +2,6 @@ import numpy as np import tensorflow as tf from deepmd.common import j_must_have, j_must_have_d, j_have -from deepmd.Model import Model from deepmd.RunOptions import global_tf_float_precision from deepmd.RunOptions import global_np_float_precision @@ -14,7 +13,7 @@ assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" op_module = tf.load_op_library(module_path + "libop_abi.so") -class DescrptSeA (Model): +class DescrptSeA (): def __init__ (self, jdata): # descrpt config self.use_smooth = False @@ -68,9 +67,6 @@ def get_rcut (self) : def get_ntypes (self) : return self.ntypes - def get_type_map (self) : - return self.type_map - def get_dim_out (self) : return self.filter_neuron[-1] * self.n_axis_neuron @@ -129,10 +125,9 @@ def build (self, davg = None, dstd = None, suffix = '', - reuse_attr = None, - reuse_weights = None): + reuse = None): - with tf.variable_scope('model_attr' + suffix, reuse = reuse_attr) : + with tf.variable_scope('model_attr' + suffix, reuse = reuse) : if davg is None: davg = np.zeros([self.ntypes, self.ndescrpt]) if dstd is None: @@ -171,7 +166,7 @@ def build (self, sel_a = self.sel_a, sel_r = self.sel_r) - self.dout = self._pass_filter(self.descrpt, natoms, suffix = suffix, reuse = reuse_weights) + self.dout = self._pass_filter(self.descrpt, natoms, suffix = suffix, reuse = reuse) return self.dout diff --git a/source/train/ModelSeR.py b/source/train/DescrptSeR.py similarity index 55% rename from source/train/ModelSeR.py rename to source/train/DescrptSeR.py index 11e1974587..18ca1f9b42 100644 --- a/source/train/ModelSeR.py +++ b/source/train/DescrptSeR.py @@ -2,7 +2,6 @@ import numpy as np import tensorflow as tf from deepmd.common import j_must_have, j_must_have_d, j_have -from deepmd.Model import Model from deepmd.RunOptions import global_tf_float_precision from deepmd.RunOptions import global_np_float_precision @@ -14,7 +13,7 @@ assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" op_module = tf.load_op_library(module_path + "libop_abi.so") -class ModelSeR (Model): +class DescrptSeR (): def __init__ (self, jdata): # descrpt config self.use_smooth = False @@ -29,14 +28,6 @@ def __init__ (self, jdata): self.rcut_smth = jdata['rcut_smth'] else : self.rcut_smth = self.rcut - # fparam - self.numb_fparam = 0 - if j_have(jdata, 'numb_fparam') : - self.numb_fparam = jdata['numb_fparam'] - # type_map - self.type_map = [] - if j_have(jdata, 'type_map') : - self.numb_fparam = jdata['type_map'] # filter of smooth version if j_have(jdata, 'coord_norm') : self.coord_norm = jdata['coord_norm'] @@ -53,24 +44,6 @@ def __init__ (self, jdata): self.ndescrpt_a = self.nnei_a * 4 self.ndescrpt_r = self.nnei_r * 1 self.ndescrpt = self.nnei_r - # network size - self.n_neuron = j_must_have_d (jdata, 'fitting_neuron', ['n_neuron']) - self.resnet_dt = True - if j_have(jdata, 'resnet_dt') : - warnings.warn("the key \"%s\" is deprecated, please use \"%s\" instead" % ('resnet_dt','fitting_resnet_dt')) - self.resnet_dt = jdata['resnet_dt'] - if j_have(jdata, 'fitting_resnet_dt') : - self.resnet_dt = jdata['fitting_resnet_dt'] - self.type_fitting_net = False - - # short-range tab - if 'use_srtab' in jdata : - self.srtab = TabInter(jdata['use_srtab']) - self.smin_alpha = j_must_have(jdata, 'smin_alpha') - self.sw_rmin = j_must_have(jdata, 'sw_rmin') - self.sw_rmax = j_must_have(jdata, 'sw_rmax') - else : - self.srtab = None self.seed = None if j_have (jdata, 'seed') : @@ -83,11 +56,8 @@ def get_rcut (self) : def get_ntypes (self) : return self.ntypes - def get_numb_fparam (self) : - return self.numb_fparam - - def get_type_map (self) : - return self.type_map + def get_dim_out (self) : + return self.filter_neuron[-1] def compute_dstats (self, data_coord, @@ -123,21 +93,17 @@ def compute_dstats (self, return davg, dstd - - def build_interaction (self, - coord_, - atype_, - natoms, - box, - mesh, - fparam, - davg = None, - dstd = None, - bias_atom_e = None, - suffix = '', - reuse_attr = None, - reuse_weights = None): - with tf.variable_scope('model_attr' + suffix, reuse = reuse_attr) : + def build (self, + coord_, + atype_, + natoms, + box, + mesh, + davg = None, + dstd = None, + suffix = '', + reuse = None): + with tf.variable_scope('model_attr' + suffix, reuse = reuse) : if davg is None: davg = np.zeros([self.ntypes, self.ndescrpt]) if dstd is None: @@ -148,12 +114,6 @@ def build_interaction (self, t_ntypes = tf.constant(self.ntypes, name = 'ntypes', dtype = tf.int32) - t_dfparam = tf.constant(self.numb_fparam, - name = 'dfparam', - dtype = tf.int32) - t_tmap = tf.constant(' '.join(self.type_map), - name = 'tmap', - dtype = tf.string) self.t_avg = tf.get_variable('t_avg', davg.shape, dtype = global_tf_float_precision, @@ -164,27 +124,15 @@ def build_interaction (self, dtype = global_tf_float_precision, trainable = False, initializer = tf.constant_initializer(dstd, dtype = global_tf_float_precision)) - if self.srtab is not None : - tab_info, tab_data = self.srtab.get() - self.tab_info = tf.get_variable('t_tab_info', - tab_info.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) - self.tab_data = tf.get_variable('t_tab_data', - tab_data.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) coord = tf.reshape (coord_, [-1, natoms[1] * 3]) atype = tf.reshape (atype_, [-1, natoms[1]]) - descrpt, descrpt_deriv, rij, nlist \ + self.descrpt, self.descrpt_deriv, self.rij, self.nlist \ = op_module.descrpt_se_r (coord, atype, natoms, - box, + box, mesh, self.t_avg, self.t_std, @@ -192,144 +140,48 @@ def build_interaction (self, rcut_smth = self.rcut_smth, sel = self.sel_r) - descrpt_reshape = tf.reshape(descrpt, [-1, self.ndescrpt]) - - atom_ener = self.build_atom_net (descrpt_reshape, - fparam, - natoms, - bias_atom_e = bias_atom_e, - reuse = reuse_weights, - suffix = suffix) - - if self.srtab is not None : - sw_lambda, sw_deriv \ - = op_module.soft_min_switch(atype, - rij, - nlist, - natoms, - sel_a = self.sel_a, - sel_r = self.sel_r, - alpha = self.smin_alpha, - rmin = self.sw_rmin, - rmax = self.sw_rmax) - inv_sw_lambda = 1.0 - sw_lambda - # NOTICE: - # atom energy is not scaled, - # force and virial are scaled - tab_atom_ener, tab_force, tab_atom_virial \ - = op_module.tab_inter(self.tab_info, - self.tab_data, - atype, - rij, - nlist, - natoms, - sw_lambda, - sel_a = self.sel_a, - sel_r = self.sel_r) - energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, natoms[0]]) - tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) - atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener - energy_raw = tab_atom_ener + atom_ener - else : - energy_raw = atom_ener - - energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'o_atom_energy'+suffix) - energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='o_energy'+suffix) - - net_deriv_tmp = tf.gradients (atom_ener, descrpt_reshape) - net_deriv = net_deriv_tmp[0] - net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) + self.dout = self._pass_filter(self.descrpt, natoms, suffix = suffix, reuse = reuse) - force = op_module.prod_force_se_r (net_deriv_reshape, - descrpt_deriv, - nlist, - natoms) - if self.srtab is not None : - sw_force \ - = op_module.soft_min_force(energy_diff, - sw_deriv, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - force = force + sw_force + tab_force + return self.dout - force = tf.reshape (force, [-1, 3 * natoms[1]], name = "o_force"+suffix) + def prod_force_virial(self, atom_ener, natoms) : + [net_deriv] = tf.gradients (atom_ener, self.descrpt) + net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) + force \ + = op_module.prod_force_se_r (net_deriv_reshape, + self.descrpt_deriv, + self.nlist, + natoms) virial, atom_virial \ = op_module.prod_virial_se_r (net_deriv_reshape, - descrpt_deriv, - rij, - nlist, - natoms) - if self.srtab is not None : - sw_virial, sw_atom_virial \ - = op_module.soft_min_virial (energy_diff, - sw_deriv, - rij, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - atom_virial = atom_virial + sw_atom_virial + tab_atom_virial - virial = virial + sw_virial \ - + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, natoms[1], 9]), axis = 1) - - virial = tf.reshape (virial, [-1, 9], name = "o_virial"+suffix) - atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "o_atom_virial"+suffix) - - return energy, force, virial, energy_raw, atom_virial - - - def build_atom_net (self, - inputs, - fparam, - natoms, - bias_atom_e = None, - reuse = None, - suffix = '') : + self.descrpt_deriv, + self.rij, + self.nlist, + natoms) + return force, virial, atom_virial + + + def _pass_filter(self, + inputs, + natoms, + reuse = None, + suffix = '') : start_index = 0 inputs = tf.reshape(inputs, [-1, self.ndescrpt * natoms[0]]) shape = inputs.get_shape().as_list() - if bias_atom_e is not None : - assert(len(bias_atom_e) == self.ntypes) - + output = [] for type_i in range(self.ntypes): - # cut-out inputs inputs_i = tf.slice (inputs, [ 0, start_index* self.ndescrpt], [-1, natoms[2+type_i]* self.ndescrpt] ) inputs_i = tf.reshape(inputs_i, [-1, self.ndescrpt]) + layer = self._filter_r(inputs_i, name='filter_type_'+str(type_i)+suffix, natoms=natoms, reuse=reuse, seed = self.seed) + layer = tf.reshape(layer, [-1, natoms[2+type_i] * self.get_dim_out()]) + output.append(layer) start_index += natoms[2+type_i] - if bias_atom_e is None : - type_bias_ae = 0.0 - else : - type_bias_ae = bias_atom_e[type_i] - - # compute atom energy - layer = self._filter_r(inputs_i, name='filter_r_type_'+str(type_i)+suffix, natoms=natoms, reuse=reuse, seed = self.seed) - if self.numb_fparam > 0 : - ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) - ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) - ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) - layer = tf.concat([layer, ext_fparam], axis = 1) - for ii in range(0,len(self.n_neuron)) : - if ii >= 1 and self.n_neuron[ii] == self.n_neuron[ii-1] : - layer+= self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed, use_timestep = self.resnet_dt) - else : - layer = self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) - final_layer = self.one_layer(layer, 1, activation_fn = None, bavg = type_bias_ae, name='final_layer_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) - final_layer = tf.reshape(final_layer, [-1, natoms[2+type_i]]) - # final_layer = tf.cond (tf.equal(natoms[2+type_i], 0), lambda: tf.zeros((0, 0), dtype=global_tf_float_precision), lambda : tf.reshape(final_layer, [-1, natoms[2+type_i]])) - - # concat the results - if type_i == 0: - outs = final_layer - else: - outs = tf.concat([outs, final_layer], axis = 1) - - return tf.reshape(outs, [-1]) - + output = tf.concat(output, axis = 1) + return output def _compute_dstats_sys_se_r (self, data_coord, @@ -452,4 +304,3 @@ def _filter_r(self, result = tf.reduce_mean(xyz_scatter, axis = 1) * res_rescale return result - diff --git a/source/train/EnerFitting.py b/source/train/EnerFitting.py new file mode 100644 index 0000000000..f8855e1998 --- /dev/null +++ b/source/train/EnerFitting.py @@ -0,0 +1,133 @@ +import os,warnings +import numpy as np +import tensorflow as tf + +from deepmd.common import j_must_have, j_must_have_d, j_have + +from deepmd.RunOptions import global_tf_float_precision +from deepmd.RunOptions import global_np_float_precision +from deepmd.RunOptions import global_ener_float_precision +from deepmd.RunOptions import global_cvt_2_tf_float +from deepmd.RunOptions import global_cvt_2_ener_float + +class EnerFitting (): + def __init__ (self, jdata, descrpt): + # model param + self.ntypes = descrpt.get_ntypes() + self.dim_descrpt = descrpt.get_dim_out() + # fparam + self.numb_fparam = 0 + if j_have(jdata, 'numb_fparam') : + self.numb_fparam = jdata['numb_fparam'] + # network size + self.n_neuron = j_must_have_d (jdata, 'fitting_neuron', ['n_neuron']) + self.resnet_dt = True + if j_have(jdata, 'resnet_dt') : + warnings.warn("the key \"%s\" is deprecated, please use \"%s\" instead" % ('resnet_dt','fitting_resnet_dt')) + self.resnet_dt = jdata['resnet_dt'] + if j_have(jdata, 'fitting_resnet_dt') : + self.resnet_dt = jdata['fitting_resnet_dt'] + + self.seed = None + if j_have (jdata, 'seed') : + self.seed = jdata['seed'] + self.useBN = False + + def get_numb_fparam(self) : + return self.numb_fparam + + def build (self, + inputs, + fparam, + natoms, + bias_atom_e = None, + reuse = None, + suffix = '') : + with tf.variable_scope('model_attr' + suffix, reuse = reuse) : + t_dfparam = tf.constant(self.numb_fparam, + name = 'dfparam', + dtype = tf.int32) + start_index = 0 + inputs = tf.reshape(inputs, [-1, self.dim_descrpt * natoms[0]]) + shape = inputs.get_shape().as_list() + + if bias_atom_e is not None : + assert(len(bias_atom_e) == self.ntypes) + + for type_i in range(self.ntypes): + # cut-out inputs + inputs_i = tf.slice (inputs, + [ 0, start_index* self.dim_descrpt], + [-1, natoms[2+type_i]* self.dim_descrpt] ) + inputs_i = tf.reshape(inputs_i, [-1, self.dim_descrpt]) + start_index += natoms[2+type_i] + if bias_atom_e is None : + type_bias_ae = 0.0 + else : + type_bias_ae = bias_atom_e[type_i] + + layer = inputs_i + if self.numb_fparam > 0 : + ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) + ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) + ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) + layer = tf.concat([layer, ext_fparam], axis = 1) + for ii in range(0,len(self.n_neuron)) : + if ii >= 1 and self.n_neuron[ii] == self.n_neuron[ii-1] : + layer+= self._one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed, use_timestep = self.resnet_dt) + else : + layer = self._one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) + final_layer = self._one_layer(layer, 1, activation_fn = None, bavg = type_bias_ae, name='final_layer_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) + final_layer = tf.reshape(final_layer, [-1, natoms[2+type_i]]) + + # concat the results + if type_i == 0: + outs = final_layer + else: + outs = tf.concat([outs, final_layer], axis = 1) + + return tf.reshape(outs, [-1]) + + + def _one_layer(self, + inputs, + outputs_size, + activation_fn=tf.nn.tanh, + stddev=1.0, + bavg=0.0, + name='linear', + reuse=None, + seed=None, + use_timestep = False): + with tf.variable_scope(name, reuse=reuse): + shape = inputs.get_shape().as_list() + w = tf.get_variable('matrix', + [shape[1], outputs_size], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev/np.sqrt(shape[1]+outputs_size), seed = seed)) + b = tf.get_variable('bias', + [outputs_size], + global_tf_float_precision, + tf.random_normal_initializer(stddev=stddev, mean = bavg, seed = seed)) + hidden = tf.matmul(inputs, w) + b + if activation_fn != None and use_timestep : + idt = tf.get_variable('idt', + [outputs_size], + global_tf_float_precision, + tf.random_normal_initializer(stddev=0.001, mean = 0.1, seed = seed)) + if activation_fn != None: + if self.useBN: + None + # hidden_bn = self._batch_norm(hidden, name=name+'_normalization', reuse=reuse) + # return activation_fn(hidden_bn) + else: + if use_timestep : + return activation_fn(hidden) * idt + else : + return activation_fn(hidden) + else: + if self.useBN: + None + # return self._batch_norm(hidden, name=name+'_normalization', reuse=reuse) + else: + return hidden diff --git a/source/train/Model.py b/source/train/Model.py index 3ed8a30dd5..c51cbf4bee 100644 --- a/source/train/Model.py +++ b/source/train/Model.py @@ -1,5 +1,7 @@ +import os,warnings import numpy as np import tensorflow as tf +from deepmd.common import j_must_have, j_must_have_d, j_have from deepmd.RunOptions import global_tf_float_precision from deepmd.RunOptions import global_np_float_precision @@ -8,6 +10,49 @@ from deepmd.RunOptions import global_cvt_2_ener_float class Model() : + def __init__ (self, jdata, descrpt, fitting): + self.descrpt = descrpt + self.rcut = self.descrpt.get_rcut() + self.ntypes = self.descrpt.get_ntypes() + # fparam + self.numb_fparam = 0 + if j_have(jdata, 'numb_fparam') : + self.numb_fparam = jdata['numb_fparam'] + # type_map + self.type_map = [] + if j_have(jdata, 'type_map') : + self.numb_fparam = jdata['type_map'] + # fitting + self.fitting = fitting + self.numb_fparam = self.fitting.get_numb_fparam() + + # short-range tab + if 'use_srtab' in jdata : + self.srtab = TabInter(jdata['use_srtab']) + self.smin_alpha = j_must_have(jdata, 'smin_alpha') + self.sw_rmin = j_must_have(jdata, 'sw_rmin') + self.sw_rmax = j_must_have(jdata, 'sw_rmax') + else : + self.srtab = None + + self.seed = None + if j_have (jdata, 'seed') : + self.seed = jdata['seed'] + self.useBN = False + + + def get_rcut (self) : + return self.rcut + + def get_ntypes (self) : + return self.ntypes + + def get_numb_fparam (self) : + return self.numb_fparam + + def get_type_map (self) : + return self.type_map + def data_stat(self, data): all_stat_coord = [] all_stat_box = [] @@ -35,48 +80,132 @@ def data_stat(self, data): # self._message("computed energy bias") return davg, dstd, bias_atom_e + + def compute_dstats (self, + data_coord, + data_box, + data_atype, + natoms_vec, + mesh, + reuse = None) : + return self.descrpt.compute_dstats(data_coord, data_box, data_atype, natoms_vec, mesh, reuse) - def one_layer(self, - inputs, - outputs_size, - activation_fn=tf.nn.tanh, - stddev=1.0, - bavg=0.0, - name='linear', - reuse=None, - seed=None, - use_timestep = False): - with tf.variable_scope(name, reuse=reuse): - shape = inputs.get_shape().as_list() - w = tf.get_variable('matrix', - [shape[1], outputs_size], - global_tf_float_precision, - tf.random_normal_initializer(stddev=stddev/np.sqrt(shape[1]+outputs_size), seed = seed)) - b = tf.get_variable('bias', - [outputs_size], - global_tf_float_precision, - tf.random_normal_initializer(stddev=stddev, mean = bavg, seed = seed)) - hidden = tf.matmul(inputs, w) + b - if activation_fn != None and use_timestep : - idt = tf.get_variable('idt', - [outputs_size], - global_tf_float_precision, - tf.random_normal_initializer(stddev=0.001, mean = 0.1, seed = seed)) - - if activation_fn != None: - if self.useBN: - None - # hidden_bn = self._batch_norm(hidden, name=name+'_normalization', reuse=reuse) - # return activation_fn(hidden_bn) - else: - if use_timestep : - return activation_fn(hidden) * idt - else : - return activation_fn(hidden) - else: - if self.useBN: - None - # return self._batch_norm(hidden, name=name+'_normalization', reuse=reuse) - else: - return hidden + def build_interaction (self, + coord_, + atype_, + natoms, + box, + mesh, + fparam, + davg = None, + dstd = None, + bias_atom_e = None, + suffix = '', + reuse = None): + + with tf.variable_scope('model_attr' + suffix, reuse = reuse) : + t_tmap = tf.constant(' '.join(self.type_map), + name = 'tmap', + dtype = tf.string) + + if self.srtab is not None : + tab_info, tab_data = self.srtab.get() + self.tab_info = tf.get_variable('t_tab_info', + tab_info.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) + self.tab_data = tf.get_variable('t_tab_data', + tab_data.shape, + dtype = tf.float64, + trainable = False, + initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) + + coord = tf.reshape (coord_, [-1, natoms[1] * 3]) + atype = tf.reshape (atype_, [-1, natoms[1]]) + + descrpt = self.descrpt.build(coord_, + atype_, + natoms, + box, + mesh, + davg = davg, + dstd = dstd, + suffix = suffix, + reuse = reuse) + + atom_ener = self.fitting.build (descrpt, + fparam, + natoms, + bias_atom_e = bias_atom_e, + reuse = reuse, + suffix = suffix) + + if self.srtab is not None : + sw_lambda, sw_deriv \ + = op_module.soft_min_switch(atype, + rij, + nlist, + natoms, + sel_a = self.sel_a, + sel_r = self.sel_r, + alpha = self.smin_alpha, + rmin = self.sw_rmin, + rmax = self.sw_rmax) + inv_sw_lambda = 1.0 - sw_lambda + # NOTICE: + # atom energy is not scaled, + # force and virial are scaled + tab_atom_ener, tab_force, tab_atom_virial \ + = op_module.tab_inter(self.tab_info, + self.tab_data, + atype, + rij, + nlist, + natoms, + sw_lambda, + sel_a = self.sel_a, + sel_r = self.sel_r) + energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, natoms[0]]) + tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) + atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener + energy_raw = tab_atom_ener + atom_ener + else : + energy_raw = atom_ener + + energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'o_atom_energy'+suffix) + energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='o_energy'+suffix) + + force, virial, atom_virial \ + = self.descrpt.prod_force_virial (atom_ener, natoms) + + if self.srtab is not None : + sw_force \ + = op_module.soft_min_force(energy_diff, + sw_deriv, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + force = force + sw_force + tab_force + + force = tf.reshape (force, [-1, 3 * natoms[1]], name = "o_force"+suffix) + + if self.srtab is not None : + sw_virial, sw_atom_virial \ + = op_module.soft_min_virial (energy_diff, + sw_deriv, + rij, + nlist, + natoms, + n_a_sel = self.nnei_a, + n_r_sel = self.nnei_r) + atom_virial = atom_virial + sw_atom_virial + tab_atom_virial + virial = virial + sw_virial \ + + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, natoms[1], 9]), axis = 1) + + virial = tf.reshape (virial, [-1, 9], name = "o_virial"+suffix) + atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "o_atom_virial"+suffix) + + return energy, force, virial, energy_raw, atom_virial diff --git a/source/train/ModelLocFrame.py b/source/train/ModelLocFrame.py deleted file mode 100644 index 4416f74283..0000000000 --- a/source/train/ModelLocFrame.py +++ /dev/null @@ -1,380 +0,0 @@ -import os -import numpy as np -import tensorflow as tf -from deepmd.common import j_must_have, j_must_have_d, j_have -from deepmd.Model import Model - -from deepmd.RunOptions import global_tf_float_precision -from deepmd.RunOptions import global_np_float_precision -from deepmd.RunOptions import global_ener_float_precision -from deepmd.RunOptions import global_cvt_2_tf_float -from deepmd.RunOptions import global_cvt_2_ener_float - -module_path = os.path.dirname(os.path.realpath(__file__)) + "/" -assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" -op_module = tf.load_op_library(module_path + "libop_abi.so") - -class ModelLocFrame(Model) : - def __init__(self, jdata) : - # descrpt config - self.sel_a = j_must_have (jdata, 'sel_a') - self.sel_r = j_must_have (jdata, 'sel_r') - self.ntypes = len(self.sel_a) - assert(self.ntypes == len(self.sel_r)) - self.rcut_a = -1 - self.rcut_r = j_must_have (jdata, 'rcut') - # axis - self.axis_rule = j_must_have (jdata, 'axis_rule') - # fparam - self.numb_fparam = 0 - if j_have(jdata, 'numb_fparam') : - self.numb_fparam = jdata['numb_fparam'] - # type_map - self.type_map = [] - if j_have(jdata, 'type_map') : - self.numb_fparam = jdata['type_map'] - # norm coord - if j_have(jdata, 'coord_norm') : - self.coord_norm = jdata['coord_norm'] - else : - self.coord_norm = True - # numb of neighbors and numb of descrptors - self.nnei_a = np.cumsum(self.sel_a)[-1] - self.nnei_r = np.cumsum(self.sel_r)[-1] - self.nnei = self.nnei_a + self.nnei_r - self.ndescrpt_a = self.nnei_a * 4 - self.ndescrpt_r = self.nnei_r * 1 - self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r - # network size - self.n_neuron = j_must_have_d (jdata, 'fitting_neuron', ['n_neuron']) - self.resnet_dt = True - if j_have(jdata, 'resnet_dt') : - warnings.warn("the key \"%s\" is deprecated, please use \"%s\" instead" % ('resnet_dt','fitting_resnet_dt')) - self.resnet_dt = jdata['resnet_dt'] - if j_have(jdata, 'fitting_resnet_dt') : - self.resnet_dt = jdata['fitting_resnet_dt'] - # short-range tab - if 'use_srtab' in jdata : - self.srtab = TabInter(jdata['use_srtab']) - self.smin_alpha = j_must_have(jdata, 'smin_alpha') - self.sw_rmin = j_must_have(jdata, 'sw_rmin') - self.sw_rmax = j_must_have(jdata, 'sw_rmax') - else : - self.srtab = None - - self.seed = None - if j_have (jdata, 'seed') : - self.seed = jdata['seed'] - self.useBN = False - - def get_rcut(self) : - return self.rcut_r - - def get_ntypes(self) : - return self.ntypes - - def get_numb_fparam (self) : - return self.numb_fparam - - def get_type_map (self) : - return self.type_map - - def compute_dstats (self, - data_coord, - data_box, - data_atype, - natoms_vec, - mesh, - reuse = None) : - all_davg = [] - all_dstd = [] - if True: - sumv = [] - sumn = [] - sumv2 = [] - for cc,bb,tt,nn,mm in zip(data_coord,data_box,data_atype,natoms_vec,mesh) : - sysv,sysv2,sysn \ - = self._compute_dstats_sys_nonsmth(cc,bb,tt,nn,mm,reuse) - sumv.append(sysv) - sumn.append(sysn) - sumv2.append(sysv2) - sumv = np.sum(sumv, axis = 0) - sumn = np.sum(sumn, axis = 0) - sumv2 = np.sum(sumv2, axis = 0) - for type_i in range(self.ntypes) : - davg = sumv[type_i] / sumn[type_i] - dstd = self._compute_std(sumv2[type_i], sumv[type_i], sumn[type_i]) - for ii in range (len(dstd)) : - if (np.abs(dstd[ii]) < 1e-2) : - dstd[ii] = 1e-2 - all_davg.append(davg) - all_dstd.append(dstd) - davg = np.array(all_davg) - dstd = np.array(all_dstd) - return davg, dstd - - - def build_interaction (self, - coord_, - atype_, - natoms, - box, - mesh, - fparam, - davg = None, - dstd = None, - bias_atom_e = None, - suffix = '', - reuse_attr = None, - reuse_weights = None): - with tf.variable_scope('model_attr' + suffix, reuse = reuse_attr) : - if davg is None: - davg = np.zeros([self.ntypes, self.ndescrpt]) - if dstd is None: - dstd = np.ones ([self.ntypes, self.ndescrpt]) - t_rcut = tf.constant(np.max([self.rcut_r, self.rcut_a]), - name = 'rcut', - dtype = global_tf_float_precision) - t_ntypes = tf.constant(self.ntypes, - name = 'ntypes', - dtype = tf.int32) - t_dfparam = tf.constant(self.numb_fparam, - name = 'dfparam', - dtype = tf.int32) - t_tmap = tf.constant(' '.join(self.type_map), - name = 'tmap', - dtype = tf.string) - self.t_avg = tf.get_variable('t_avg', - davg.shape, - dtype = global_tf_float_precision, - trainable = False, - initializer = tf.constant_initializer(davg, dtype = global_tf_float_precision)) - self.t_std = tf.get_variable('t_std', - dstd.shape, - dtype = global_tf_float_precision, - trainable = False, - initializer = tf.constant_initializer(dstd, dtype = global_tf_float_precision)) - if self.srtab is not None : - tab_info, tab_data = self.srtab.get() - self.tab_info = tf.get_variable('t_tab_info', - tab_info.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) - self.tab_data = tf.get_variable('t_tab_data', - tab_data.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) - - coord = tf.reshape (coord_, [-1, natoms[1] * 3]) - atype = tf.reshape (atype_, [-1, natoms[1]]) - - descrpt, descrpt_deriv, rij, nlist, axis \ - = op_module.descrpt (coord, - atype, - natoms, - box, - mesh, - self.t_avg, - self.t_std, - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - sel_a = self.sel_a, - sel_r = self.sel_r, - axis_rule = self.axis_rule) - - descrpt_reshape = tf.reshape(descrpt, [-1, self.ndescrpt]) - - atom_ener = self.build_atom_net (descrpt_reshape, fparam, natoms, bias_atom_e = bias_atom_e, reuse = reuse_weights) - - if self.srtab is not None : - sw_lambda, sw_deriv \ - = op_module.soft_min_switch(atype, - rij, - nlist, - natoms, - sel_a = self.sel_a, - sel_r = self.sel_r, - alpha = self.smin_alpha, - rmin = self.sw_rmin, - rmax = self.sw_rmax) - inv_sw_lambda = 1.0 - sw_lambda - # NOTICE: - # atom energy is not scaled, - # force and virial are scaled - tab_atom_ener, tab_force, tab_atom_virial \ - = op_module.tab_inter(self.tab_info, - self.tab_data, - atype, - rij, - nlist, - natoms, - sw_lambda, - sel_a = self.sel_a, - sel_r = self.sel_r) - energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, natoms[0]]) - tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) - atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener - energy_raw = tab_atom_ener + atom_ener - else : - energy_raw = atom_ener - - energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'o_atom_energy'+suffix) - energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='o_energy'+suffix) - - net_deriv_tmp = tf.gradients (atom_ener, descrpt_reshape) - net_deriv = net_deriv_tmp[0] - net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) - - force = op_module.prod_force (net_deriv_reshape, - descrpt_deriv, - nlist, - axis, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - if self.srtab is not None : - sw_force \ - = op_module.soft_min_force(energy_diff, - sw_deriv, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - force = force + sw_force + tab_force - - force = tf.reshape (force, [-1, 3 * natoms[1]], name = "o_force"+suffix) - - virial, atom_virial \ - = op_module.prod_virial (net_deriv_reshape, - descrpt_deriv, - rij, - nlist, - axis, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - if self.srtab is not None : - sw_virial, sw_atom_virial \ - = op_module.soft_min_virial (energy_diff, - sw_deriv, - rij, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - atom_virial = atom_virial + sw_atom_virial + tab_atom_virial - virial = virial + sw_virial \ - + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, natoms[1], 9]), axis = 1) - - virial = tf.reshape (virial, [-1, 9], name = "o_virial"+suffix) - atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "o_atom_virial"+suffix) - - return energy, force, virial, energy_raw, atom_virial - - - def build_atom_net (self, - inputs, - fparam, - natoms, - bias_atom_e = None, - reuse = None, - suffix = '') : - start_index = 0 - inputs = tf.reshape(inputs, [-1, self.ndescrpt * natoms[0]]) - shape = inputs.get_shape().as_list() - if bias_atom_e is not None : - assert(len(bias_atom_e) == self.ntypes) - - for type_i in range(self.ntypes): - # cut-out inputs - inputs_i = tf.slice (inputs, - [ 0, start_index* self.ndescrpt], - [-1, natoms[2+type_i]* self.ndescrpt] ) - inputs_i = tf.reshape(inputs_i, [-1, self.ndescrpt]) - start_index += natoms[2+type_i] - if bias_atom_e is None : - type_bias_ae = 0.0 - else : - type_bias_ae = bias_atom_e[type_i] - - # compute atom energy - if self.numb_fparam > 0 : - ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) - ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) - ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) - layer = tf.concat([layer, ext_fparam], axis = 1) - layer = self.one_layer(inputs_i, self.n_neuron[0], name='layer_0_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) - for ii in range(1,len(self.n_neuron)) : - layer = self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) - - final_layer = self.one_layer(layer, 1, activation_fn = None, bavg = type_bias_ae, name='final_layer_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) - final_layer = tf.reshape(final_layer, [-1, natoms[2+type_i]]) - # final_layer = tf.cond (tf.equal(natoms[2+type_i], 0), lambda: tf.zeros((0, 0), dtype=global_tf_float_precision), lambda : tf.reshape(final_layer, [-1, natoms[2+type_i]])) - - # concat the results - if type_i == 0: - outs = final_layer - else: - outs = tf.concat([outs, final_layer], axis = 1) - - return tf.reshape(outs, [-1]) - - - def _compute_dstats_sys_nonsmth (self, - data_coord, - data_box, - data_atype, - natoms_vec, - mesh, - reuse = None) : - avg_zero = np.zeros([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) - std_ones = np.ones ([self.ntypes,self.ndescrpt]).astype(global_np_float_precision) - sub_graph = tf.Graph() - with sub_graph.as_default(): - descrpt, descrpt_deriv, rij, nlist, axis \ - = op_module.descrpt (tf.constant(data_coord), - tf.constant(data_atype), - tf.constant(natoms_vec, dtype = tf.int32), - tf.constant(data_box), - tf.constant(mesh), - tf.constant(avg_zero), - tf.constant(std_ones), - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - sel_a = self.sel_a, - sel_r = self.sel_r, - axis_rule = self.axis_rule) - # self.sess.run(tf.global_variables_initializer()) - # sub_sess = tf.Session(graph = sub_graph, - # config=tf.ConfigProto(intra_op_parallelism_threads=self.run_opt.num_intra_threads, - # inter_op_parallelism_threads=self.run_opt.num_inter_threads - # )) - sub_sess = tf.Session(graph = sub_graph) - dd_all = sub_sess.run(descrpt) - sub_sess.close() - natoms = natoms_vec - dd_all = np.reshape(dd_all, [-1, self.ndescrpt * natoms[0]]) - start_index = 0 - sysv = [] - sysn = [] - sysv2 = [] - for type_i in range(self.ntypes): - end_index = start_index + self.ndescrpt * natoms[2+type_i] - dd = dd_all[:, start_index:end_index] - dd = np.reshape(dd, [-1, self.ndescrpt]) - start_index = end_index - # compute - sumv = np.sum(dd, axis = 0) - sumn = dd.shape[0] - sumv2 = np.sum(np.multiply(dd,dd), axis = 0) - sysv.append(sumv) - sysn.append(sumn) - sysv2.append(sumv2) - return sysv, sysv2, sysn - - - def _compute_std (self,sumv2, sumv, sumn) : - return np.sqrt(sumv2/sumn - np.multiply(sumv/sumn, sumv/sumn)) - diff --git a/source/train/ModelSeA.py b/source/train/ModelSeA.py deleted file mode 100644 index e7f8f392f6..0000000000 --- a/source/train/ModelSeA.py +++ /dev/null @@ -1,248 +0,0 @@ -import os,warnings -import numpy as np -import tensorflow as tf -from deepmd.common import j_must_have, j_must_have_d, j_have -from deepmd.Model import Model -from deepmd.DescrptSeA import DescrptSeA - -from deepmd.RunOptions import global_tf_float_precision -from deepmd.RunOptions import global_np_float_precision -from deepmd.RunOptions import global_ener_float_precision -from deepmd.RunOptions import global_cvt_2_tf_float -from deepmd.RunOptions import global_cvt_2_ener_float - -module_path = os.path.dirname(os.path.realpath(__file__)) + "/" -assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" -op_module = tf.load_op_library(module_path + "libop_abi.so") - -class ModelSeA (Model): - def __init__ (self, jdata): - self.descrpt = DescrptSeA(jdata) - self.ntypes = self.descrpt.get_ntypes() - # fparam - self.numb_fparam = 0 - if j_have(jdata, 'numb_fparam') : - self.numb_fparam = jdata['numb_fparam'] - # type_map - self.type_map = [] - if j_have(jdata, 'type_map') : - self.numb_fparam = jdata['type_map'] - # network size - self.n_neuron = j_must_have_d (jdata, 'fitting_neuron', ['n_neuron']) - self.resnet_dt = True - if j_have(jdata, 'resnet_dt') : - warnings.warn("the key \"%s\" is deprecated, please use \"%s\" instead" % ('resnet_dt','fitting_resnet_dt')) - self.resnet_dt = jdata['resnet_dt'] - if j_have(jdata, 'fitting_resnet_dt') : - self.resnet_dt = jdata['fitting_resnet_dt'] - if j_have(jdata, 'type_fitting_net') : - self.type_fitting_net = jdata['type_fitting_net'] - else : - self.type_fitting_net = False - - # short-range tab - if 'use_srtab' in jdata : - self.srtab = TabInter(jdata['use_srtab']) - self.smin_alpha = j_must_have(jdata, 'smin_alpha') - self.sw_rmin = j_must_have(jdata, 'sw_rmin') - self.sw_rmax = j_must_have(jdata, 'sw_rmax') - else : - self.srtab = None - - self.seed = None - if j_have (jdata, 'seed') : - self.seed = jdata['seed'] - self.useBN = False - - - def get_rcut (self) : - return self.descrpt.get_rcut() - - def get_ntypes (self) : - return self.ntypes - - def get_numb_fparam (self) : - return self.numb_fparam - - def get_type_map (self) : - return self.type_map - - def compute_dstats (self, - data_coord, - data_box, - data_atype, - natoms_vec, - mesh, - reuse = None) : - return self.descrpt.compute_dstats(data_coord, data_box, data_atype, natoms_vec, mesh, reuse) - - - def build_interaction (self, - coord_, - atype_, - natoms, - box, - mesh, - fparam, - davg = None, - dstd = None, - bias_atom_e = None, - suffix = '', - reuse_attr = None, - reuse_weights = None): - - with tf.variable_scope('model_attr' + suffix, reuse = reuse_attr) : - t_dfparam = tf.constant(self.numb_fparam, - name = 'dfparam', - dtype = tf.int32) - t_tmap = tf.constant(' '.join(self.type_map), - name = 'tmap', - dtype = tf.string) - - if self.srtab is not None : - tab_info, tab_data = self.srtab.get() - self.tab_info = tf.get_variable('t_tab_info', - tab_info.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) - self.tab_data = tf.get_variable('t_tab_data', - tab_data.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) - - coord = tf.reshape (coord_, [-1, natoms[1] * 3]) - atype = tf.reshape (atype_, [-1, natoms[1]]) - - descrpt = self.descrpt.build(coord_, atype_, natoms, box, mesh, davg = davg, dstd = dstd, suffix = suffix, reuse_attr = reuse_attr, reuse_weights = reuse_weights) - - atom_ener = self.build_atom_net (descrpt, - fparam, - natoms, - bias_atom_e = bias_atom_e, - reuse = reuse_weights, - suffix = suffix) - - if self.srtab is not None : - sw_lambda, sw_deriv \ - = op_module.soft_min_switch(atype, - rij, - nlist, - natoms, - sel_a = self.sel_a, - sel_r = self.sel_r, - alpha = self.smin_alpha, - rmin = self.sw_rmin, - rmax = self.sw_rmax) - inv_sw_lambda = 1.0 - sw_lambda - # NOTICE: - # atom energy is not scaled, - # force and virial are scaled - tab_atom_ener, tab_force, tab_atom_virial \ - = op_module.tab_inter(self.tab_info, - self.tab_data, - atype, - rij, - nlist, - natoms, - sw_lambda, - sel_a = self.sel_a, - sel_r = self.sel_r) - energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, natoms[0]]) - tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) - atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener - energy_raw = tab_atom_ener + atom_ener - else : - energy_raw = atom_ener - - energy_raw = tf.reshape(energy_raw, [-1, natoms[0]], name = 'o_atom_energy'+suffix) - energy = tf.reduce_sum(global_cvt_2_ener_float(energy_raw), axis=1, name='o_energy'+suffix) - - force, virial, atom_virial \ - = self.descrpt.prod_force_virial (atom_ener, natoms) - - if self.srtab is not None : - sw_force \ - = op_module.soft_min_force(energy_diff, - sw_deriv, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - force = force + sw_force + tab_force - - force = tf.reshape (force, [-1, 3 * natoms[1]], name = "o_force"+suffix) - - if self.srtab is not None : - sw_virial, sw_atom_virial \ - = op_module.soft_min_virial (energy_diff, - sw_deriv, - rij, - nlist, - natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - atom_virial = atom_virial + sw_atom_virial + tab_atom_virial - virial = virial + sw_virial \ - + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, natoms[1], 9]), axis = 1) - - virial = tf.reshape (virial, [-1, 9], name = "o_virial"+suffix) - atom_virial = tf.reshape (atom_virial, [-1, 9 * natoms[1]], name = "o_atom_virial"+suffix) - - return energy, force, virial, energy_raw, atom_virial - - - def build_atom_net (self, - inputs, - fparam, - natoms, - bias_atom_e = None, - reuse = None, - suffix = '') : - start_index = 0 - inputs = tf.reshape(inputs, [-1, self.descrpt.get_dim_out() * natoms[0]]) - shape = inputs.get_shape().as_list() - if bias_atom_e is not None : - assert(len(bias_atom_e) == self.ntypes) - - for type_i in range(self.ntypes): - # cut-out inputs - inputs_i = tf.slice (inputs, - [ 0, start_index* self.descrpt.get_dim_out()], - [-1, natoms[2+type_i]* self.descrpt.get_dim_out()] ) - inputs_i = tf.reshape(inputs_i, [-1, self.descrpt.get_dim_out()]) - start_index += natoms[2+type_i] - if bias_atom_e is None : - type_bias_ae = 0.0 - else : - type_bias_ae = bias_atom_e[type_i] - - layer = inputs_i - if self.numb_fparam > 0 : - ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) - ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) - ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) - layer = tf.concat([layer, ext_fparam], axis = 1) - for ii in range(0,len(self.n_neuron)) : - if ii >= 1 and self.n_neuron[ii] == self.n_neuron[ii-1] : - layer+= self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed, use_timestep = self.resnet_dt) - else : - layer = self.one_layer(layer, self.n_neuron[ii], name='layer_'+str(ii)+'_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) - final_layer = self.one_layer(layer, 1, activation_fn = None, bavg = type_bias_ae, name='final_layer_type_'+str(type_i)+suffix, reuse=reuse, seed = self.seed) - final_layer = tf.reshape(final_layer, [-1, natoms[2+type_i]]) - # final_layer = tf.cond (tf.equal(natoms[2+type_i], 0), lambda: tf.zeros((0, 0), dtype=global_tf_float_precision), lambda : tf.reshape(final_layer, [-1, natoms[2+type_i]])) - - # concat the results - if type_i == 0: - outs = final_layer - else: - outs = tf.concat([outs, final_layer], axis = 1) - - return tf.reshape(outs, [-1]) - - - - - - diff --git a/source/train/Trainer.py b/source/train/Trainer.py index 93372febba..0501fb29fc 100644 --- a/source/train/Trainer.py +++ b/source/train/Trainer.py @@ -11,10 +11,15 @@ from deepmd.RunOptions import global_ener_float_precision from deepmd.RunOptions import global_cvt_2_tf_float from deepmd.RunOptions import global_cvt_2_ener_float -from ModelSeA import ModelSeA -from ModelSeR import ModelSeR -from ModelHyb import ModelHyb -from ModelLocFrame import ModelLocFrame +# from ModelSeA import ModelSeA +# from ModelSeR import ModelSeR +# from ModelHyb import ModelHyb +# from ModelLocFrame import ModelLocFrame +from EnerFitting import EnerFitting +from DescrptLocFrame import DescrptLocFrame +from DescrptSeA import DescrptSeA +from DescrptSeR import DescrptSeR +from Model import Model from tensorflow.python.framework import ops from tensorflow.python.client import timeline @@ -82,20 +87,23 @@ def _init_param(self, jdata): model_type = j_must_have(jdata, 'model_type') if model_type == 'loc_frame': model_param = j_must_have(jdata, 'model') - self.model = ModelLocFrame(model_param) + self.descrpt = DescrptLocFrame(model_param) elif model_type == 'se_a' : model_param = j_must_have(jdata, 'model') - self.model = ModelSeA(model_param) + self.descrpt = DescrptSeA(model_param) elif model_type == 'se_r' : model_param = j_must_have(jdata, 'model') - self.model = ModelSeR(model_param) - elif model_type == 'se_ar' : - model_param_a = j_must_have(jdata, 'model_a') - model_param_r = j_must_have(jdata, 'model_r') - self.model = ModelHyb(model_param_a, model_param_r) + self.descrpt = DescrptSeR(model_param) + # elif model_type == 'se_ar' : + # model_param_a = j_must_have(jdata, 'model_a') + # model_param_r = j_must_have(jdata, 'model_r') + # self.model = ModelHyb(model_param_a, model_param_r) else : raise RuntimeError('unknow model type ' + model_type) + self.fitting = EnerFitting(model_param, self.descrpt) + self.model = Model(model_param, self.descrpt, self.fitting) + self.numb_test = j_must_have (jdata, 'numb_test') self.useBN = False @@ -210,8 +218,7 @@ def _build_network(self, davg, dstd, bias_atom_e): dstd = dstd, bias_atom_e = bias_atom_e, suffix = "", - reuse_attr = False, - reuse_weights = False) + reuse = False) self.l2_l, self.l2_el, self.l2_fl, self.l2_vl, self.l2_ael \ = self.loss (self.t_natoms, \ From 62d91ad7edd3b41da28ace2bd86cb5ec6be53a67 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Thu, 27 Jun 2019 17:30:01 +0800 Subject: [PATCH 15/33] update tests, add json for se_r --- examples/train/water.json | 2 +- examples/train/water_se_r.json | 49 ++++++++++++++++++++++++++++ source/tests/test_model_loc_frame.py | 12 ++++--- source/tests/test_model_se_a.py | 12 ++++--- source/tests/test_model_se_r.py | 12 ++++--- 5 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 examples/train/water_se_r.json diff --git a/examples/train/water.json b/examples/train/water.json index 4de81e3031..7371507b27 100644 --- a/examples/train/water.json +++ b/examples/train/water.json @@ -13,7 +13,7 @@ "_comment": " if type < 0, exclude type -(type+1)", "_comment": " for water (O:0, H:1) it can be", "_comment": " [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0]", - "fitting_neuron": [240, 240, 240], + "fitting_neuron": [240, 120, 60, 30, 10], "seed": 1 }, diff --git a/examples/train/water_se_r.json b/examples/train/water_se_r.json new file mode 100644 index 0000000000..a45ed43a44 --- /dev/null +++ b/examples/train/water_se_r.json @@ -0,0 +1,49 @@ +{ + "_comment": " model parameters", + "model_type": "se_r", + "model": { + "sel_r": [46, 92], + "rcut_smth": 1.00, + "rcut": 6.00, + "filter_neuron": [5, 10, 20], + "filter_resnet_dt": false, + "fitting_neuron": [120, 120, 120], + "fitting_resnet_dt": true, + "seed": 1, + "_comment": "that's all" + }, + + "_comment": " traing controls", + "systems": ["../data/water/"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 1, + "start_lr": 0.005, + "decay_steps": 5000, + "decay_rate": 0.95, + + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + + "seed": 1, + + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 10, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training": true, + "time_training": true, + "profiling": false, + "profiling_file": "timeline.json", + + "_comment": "that's all" +} + diff --git a/source/tests/test_model_loc_frame.py b/source/tests/test_model_loc_frame.py index 67234b42fa..17b2d26a3a 100644 --- a/source/tests/test_model_loc_frame.py +++ b/source/tests/test_model_loc_frame.py @@ -8,7 +8,9 @@ from deepmd.RunOptions import RunOptions from deepmd.DataSystem import DataSystem -from deepmd.ModelLocFrame import ModelLocFrame +from deepmd.DescrptLocFrame import DescrptLocFrame +from deepmd.EnerFitting import EnerFitting +from deepmd.Model import Model from deepmd.common import j_must_have, j_must_have_d, j_have global_ener_float_precision = tf.float64 @@ -60,7 +62,10 @@ def test_model(self): bias_atom_e = data.compute_energy_shift() - model = ModelLocFrame(jdata) + descrpt = DescrptLocFrame(jdata) + fitting = EnerFitting(jdata, descrpt) + model = Model(jdata, descrpt, fitting) + davg, dstd = model.compute_dstats([test_coord], [test_box], [test_type], [natoms_vec], [default_mesh]) t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') @@ -87,8 +92,7 @@ def test_model(self): dstd = dstd, bias_atom_e = bias_atom_e, suffix = "loc_frame", - reuse_attr = False, - reuse_weights = False) + reuse = False) feed_dict_test = {t_prop_c: test_prop_c, t_energy: test_energy [:numb_test], diff --git a/source/tests/test_model_se_a.py b/source/tests/test_model_se_a.py index 333a196089..d9e66bee73 100644 --- a/source/tests/test_model_se_a.py +++ b/source/tests/test_model_se_a.py @@ -8,7 +8,9 @@ from deepmd.RunOptions import RunOptions from deepmd.DataSystem import DataSystem -from deepmd.ModelSeA import ModelSeA +from deepmd.DescrptSeA import DescrptSeA +from deepmd.EnerFitting import EnerFitting +from deepmd.Model import Model from deepmd.common import j_must_have, j_must_have_d, j_have global_ener_float_precision = tf.float64 @@ -60,7 +62,10 @@ def test_model(self): bias_atom_e = data.compute_energy_shift() - model = ModelSeA(jdata) + descrpt = DescrptSeA(jdata) + fitting = EnerFitting(jdata, descrpt) + model = Model(jdata, descrpt, fitting) + davg, dstd = model.compute_dstats([test_coord], [test_box], [test_type], [natoms_vec], [default_mesh]) t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') @@ -87,8 +92,7 @@ def test_model(self): dstd = dstd, bias_atom_e = bias_atom_e, suffix = "se_a", - reuse_attr = False, - reuse_weights = False) + reuse = False) feed_dict_test = {t_prop_c: test_prop_c, t_energy: test_energy [:numb_test], diff --git a/source/tests/test_model_se_r.py b/source/tests/test_model_se_r.py index fae15df1a5..ec48387196 100644 --- a/source/tests/test_model_se_r.py +++ b/source/tests/test_model_se_r.py @@ -8,7 +8,9 @@ from deepmd.RunOptions import RunOptions from deepmd.DataSystem import DataSystem -from deepmd.ModelSeR import ModelSeR +from deepmd.DescrptSeR import DescrptSeR +from deepmd.EnerFitting import EnerFitting +from deepmd.Model import Model from deepmd.common import j_must_have, j_must_have_d, j_have global_ener_float_precision = tf.float64 @@ -60,7 +62,10 @@ def test_model(self): bias_atom_e = data.compute_energy_shift() - model = ModelSeR(jdata) + descrpt = DescrptSeR(jdata) + fitting = EnerFitting(jdata, descrpt) + model = Model(jdata, descrpt, fitting) + davg, dstd = model.compute_dstats([test_coord], [test_box], [test_type], [natoms_vec], [default_mesh]) t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') @@ -87,8 +92,7 @@ def test_model(self): dstd = dstd, bias_atom_e = bias_atom_e, suffix = "se_r", - reuse_attr = False, - reuse_weights = False) + reuse = False) feed_dict_test = {t_prop_c: test_prop_c, t_energy: test_energy [:numb_test], From 8c5edbb8df331821fded8740127447632f342cc2 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Thu, 27 Jun 2019 21:42:19 +0800 Subject: [PATCH 16/33] new parameter convention --- examples/train/water.json | 35 +++++++----- examples/train/water_se_a.json | 31 ++++++---- examples/train/water_se_r.json | 27 ++++++--- source/tests/test_model_loc_frame.py | 8 +-- source/tests/test_model_se_a.py | 8 +-- source/tests/test_model_se_r.py | 8 +-- source/tests/water.json | 31 ++++++---- source/tests/water_se_a.json | 29 ++++++---- source/tests/water_se_r.json | 26 +++++---- source/train/DescrptSeA.py | 23 ++------ source/train/DescrptSeR.py | 15 ++--- source/train/EnerFitting.py | 5 +- source/train/Model.py | 12 +--- source/train/Trainer.py | 27 ++++----- source/train/print_old_model.py | 84 ++++++++++++++++++++++++++++ 15 files changed, 232 insertions(+), 137 deletions(-) create mode 100644 source/train/print_old_model.py diff --git a/examples/train/water.json b/examples/train/water.json index 7371507b27..066a1c083f 100644 --- a/examples/train/water.json +++ b/examples/train/water.json @@ -1,20 +1,29 @@ { "with_distrib": false, "_comment": " model parameters", - "model_type": "loc_frame", "model":{ - "sel_a": [16, 32], - "sel_r": [30, 60], - "rcut": 6.00, - "axis_rule": [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0], - "_comment": " default rule: []", - "_comment": " user defined rule: for each type provides two axes, ", - "_comment": " for each axis: (a_or_r, type, idx)", - "_comment": " if type < 0, exclude type -(type+1)", - "_comment": " for water (O:0, H:1) it can be", - "_comment": " [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0]", - "fitting_neuron": [240, 120, 60, 30, 10], - "seed": 1 + "type_map": ["O", "H"], + "descriptor": { + "type": "loc_frame", + "sel_a": [16, 32], + "sel_r": [30, 60], + "rcut": 6.00, + "axis_rule": [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0], + "_comment": " default rule: []", + "_comment": " user defined rule: for each type provides two axes, ", + "_comment": " for each axis: (a_or_r, type, idx)", + "_comment": " if type < 0, exclude type -(type+1)", + "_comment": " for water (O:0, H:1) it can be", + "_comment": " [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0]", + "_comment": " that's all" + }, + "fitting_net": { + "neuron": [240, 120, 60, 30, 10], + "resnet_dt": true, + "seed": 1, + "_comment": " that's all" + }, + "_comment": " that's all" }, "_comment": " traing controls", diff --git a/examples/train/water_se_a.json b/examples/train/water_se_a.json index c6a818385c..b460df3452 100644 --- a/examples/train/water_se_a.json +++ b/examples/train/water_se_a.json @@ -1,18 +1,25 @@ { "_comment": " model parameters", - "model_type": "se_a", "model": { - "sel_a": [46, 92], - "rcut_smth": 5.80, - "rcut": 6.00, - "filter_neuron": [25, 50, 100], - "filter_resnet_dt": false, - "axis_neuron": 16, - "fitting_neuron": [240, 240, 240], - "fitting_resnet_dt": true, - "coord_norm": true, - "type_fitting_net": false, - "seed": 1 + "type_map": ["O", "H"], + "descriptor" :{ + "type": "se_a", + "sel": [46, 92], + "rcut_smth": 5.80, + "rcut": 6.00, + "neuron": [25, 50, 100], + "resnet_dt": false, + "axis_neuron": 16, + "seed": 1, + "_comment": " that's all" + }, + "fitting_net" : { + "neuron": [240, 240, 240], + "resnet_dt": true, + "seed": 1, + "_comment": " that's all" + }, + "_comment": " that's all" }, "_comment": " traing controls", diff --git a/examples/train/water_se_r.json b/examples/train/water_se_r.json index a45ed43a44..32b88cd00f 100644 --- a/examples/train/water_se_r.json +++ b/examples/train/water_se_r.json @@ -2,15 +2,24 @@ "_comment": " model parameters", "model_type": "se_r", "model": { - "sel_r": [46, 92], - "rcut_smth": 1.00, - "rcut": 6.00, - "filter_neuron": [5, 10, 20], - "filter_resnet_dt": false, - "fitting_neuron": [120, 120, 120], - "fitting_resnet_dt": true, - "seed": 1, - "_comment": "that's all" + "type_map": ["O", "H"], + "descriptor": { + "type": "se_r", + "sel": [46, 92], + "rcut_smth": 1.00, + "rcut": 6.00, + "neuron": [5, 10, 20], + "resnet_dt": false, + "seed": 1, + "_comment": " that's all" + }, + "fitting_net" :{ + "neuron": [120, 120, 120], + "resnet_dt": true, + "seed": 1, + "_comment": "that's all" + }, + "_comment": " that's all" }, "_comment": " traing controls", diff --git a/source/tests/test_model_loc_frame.py b/source/tests/test_model_loc_frame.py index 17b2d26a3a..ee3d772b71 100644 --- a/source/tests/test_model_loc_frame.py +++ b/source/tests/test_model_loc_frame.py @@ -48,7 +48,7 @@ def test_model(self): batch_size = 1 test_size = 1 stop_batch = j_must_have(jdata, 'stop_batch') - rcut = j_must_have (jdata, 'rcut') + rcut = j_must_have (jdata['model']['descriptor'], 'rcut') data = DataSystem(systems, set_pfx, batch_size, test_size, rcut, run_opt = None) @@ -62,9 +62,9 @@ def test_model(self): bias_atom_e = data.compute_energy_shift() - descrpt = DescrptLocFrame(jdata) - fitting = EnerFitting(jdata, descrpt) - model = Model(jdata, descrpt, fitting) + descrpt = DescrptLocFrame(jdata['model']['descriptor']) + fitting = EnerFitting(jdata['model']['fitting_net'], descrpt) + model = Model(jdata['model'], descrpt, fitting) davg, dstd = model.compute_dstats([test_coord], [test_box], [test_type], [natoms_vec], [default_mesh]) diff --git a/source/tests/test_model_se_a.py b/source/tests/test_model_se_a.py index d9e66bee73..11d4d1a566 100644 --- a/source/tests/test_model_se_a.py +++ b/source/tests/test_model_se_a.py @@ -48,7 +48,7 @@ def test_model(self): batch_size = 1 test_size = 1 stop_batch = j_must_have(jdata, 'stop_batch') - rcut = j_must_have (jdata, 'rcut') + rcut = j_must_have (jdata['model']['descriptor'], 'rcut') data = DataSystem(systems, set_pfx, batch_size, test_size, rcut, run_opt = None) @@ -62,9 +62,9 @@ def test_model(self): bias_atom_e = data.compute_energy_shift() - descrpt = DescrptSeA(jdata) - fitting = EnerFitting(jdata, descrpt) - model = Model(jdata, descrpt, fitting) + descrpt = DescrptSeA(jdata['model']['descriptor']) + fitting = EnerFitting(jdata['model']['fitting_net'], descrpt) + model = Model(jdata['model'], descrpt, fitting) davg, dstd = model.compute_dstats([test_coord], [test_box], [test_type], [natoms_vec], [default_mesh]) diff --git a/source/tests/test_model_se_r.py b/source/tests/test_model_se_r.py index ec48387196..c7f3e51c1f 100644 --- a/source/tests/test_model_se_r.py +++ b/source/tests/test_model_se_r.py @@ -48,7 +48,7 @@ def test_model(self): batch_size = 1 test_size = 1 stop_batch = j_must_have(jdata, 'stop_batch') - rcut = j_must_have (jdata, 'rcut') + rcut = j_must_have (jdata['model']['descriptor'], 'rcut') data = DataSystem(systems, set_pfx, batch_size, test_size, rcut, run_opt = None) @@ -62,9 +62,9 @@ def test_model(self): bias_atom_e = data.compute_energy_shift() - descrpt = DescrptSeR(jdata) - fitting = EnerFitting(jdata, descrpt) - model = Model(jdata, descrpt, fitting) + descrpt = DescrptSeR(jdata['model']['descriptor']) + fitting = EnerFitting(jdata['model']['fitting_net'], descrpt) + model = Model(jdata['model'], descrpt, fitting) davg, dstd = model.compute_dstats([test_coord], [test_box], [test_type], [natoms_vec], [default_mesh]) diff --git a/source/tests/water.json b/source/tests/water.json index 1499f974a2..b4817fecf0 100644 --- a/source/tests/water.json +++ b/source/tests/water.json @@ -1,18 +1,25 @@ { "with_distrib": false, "_comment": " model parameters", - "use_smooth": false, - "sel_a": [16, 32], - "sel_r": [30, 60], - "rcut": 6.00, - "axis_rule": [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0], - "_comment": " default rule: []", - "_comment": " user defined rule: for each type provides two axes, ", - "_comment": " for each axis: (a_or_r, type, idx)", - "_comment": " if type < 0, exclude type -(type+1)", - "_comment": " for water (O:0, H:1) it can be", - "_comment": " [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0]", - "fitting_neuron": [240, 120, 60, 30, 10], + "model" :{ + "descriptor":{ + "type": "loc_frame", + "sel_a": [16, 32], + "sel_r": [30, 60], + "rcut": 6.00, + "axis_rule": [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0], + "_comment": " default rule: []", + "_comment": " user defined rule: for each type provides two axes, ", + "_comment": " for each axis: (a_or_r, type, idx)", + "_comment": " if type < 0, exclude type -(type+1)", + "_comment": " for water (O:0, H:1) it can be", + "_comment": " [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0]" + }, + "fitting_net" : { + "neuron": [240, 120, 60, 30, 10], + "seed": 1 + } + }, "_comment": " traing controls", "systems": ["system"], diff --git a/source/tests/water_se_a.json b/source/tests/water_se_a.json index 4b5ced3cd6..f948682368 100644 --- a/source/tests/water_se_a.json +++ b/source/tests/water_se_a.json @@ -1,16 +1,23 @@ { "_comment": " model parameters", - "use_smooth": true, - "sel_a": [46, 92], - "rcut_smth": 5.80, - "rcut": 6.00, - "filter_neuron": [25, 50, 100], - "filter_resnet_dt": false, - "axis_neuron": 16, - "fitting_neuron": [240, 240, 240], - "fitting_resnet_dt":true, - "coord_norm": true, - "type_fitting_net": false, + "model" : { + "descriptor" :{ + "type": "se_a", + "sel": [46, 92], + "rcut_smth": 5.80, + "rcut": 6.00, + "neuron": [25, 50, 100], + "resnet_dt": false, + "axis_neuron": 16, + "seed": 1 + }, + "fitting_net" : { + "neuron": [240, 240, 240], + "resnet_dt": true, + "seed": 1 + } + }, + "_comment": " traing controls", "systems": ["system"], diff --git a/source/tests/water_se_r.json b/source/tests/water_se_r.json index 9c7b1f268a..43194f42fd 100644 --- a/source/tests/water_se_r.json +++ b/source/tests/water_se_r.json @@ -1,15 +1,21 @@ { "_comment": " model parameters", - "use_smooth": true, - "sel_r": [46, 92], - "rcut_smth": 5.80, - "rcut": 6.00, - "filter_neuron": [25, 50, 100], - "filter_resnet_dt": false, - "fitting_neuron": [240, 240, 240], - "fitting_resnet_dt":true, - "coord_norm": true, - "type_fitting_net": false, + "model" : { + "descriptor" : { + "type": "se_r", + "sel": [46, 92], + "rcut_smth": 5.80, + "rcut": 6.00, + "neuron": [25, 50, 100], + "resnet_dt": false, + "seed": 1 + }, + "fitting_net" : { + "neuron": [240, 240, 240], + "resnet_dt": true, + "seed": 1 + } + }, "_comment": " traing controls", "systems": ["system"], diff --git a/source/train/DescrptSeA.py b/source/train/DescrptSeA.py index b86018b4ba..8990d7090d 100644 --- a/source/train/DescrptSeA.py +++ b/source/train/DescrptSeA.py @@ -16,11 +16,8 @@ class DescrptSeA (): def __init__ (self, jdata): # descrpt config - self.use_smooth = False - self.sel_a = j_must_have (jdata, 'sel_a') + self.sel_a = j_must_have (jdata, 'sel') self.sel_r = [ 0 for ii in range(len(self.sel_a)) ] - if j_have (jdata, 'sel_r') : - warnings.warn ('ignoring key sel_r in the json database and set sel_r to %s' % str(self.sel_r)) self.ntypes = len(self.sel_a) assert(self.ntypes == len(self.sel_r)) self.rcut_a = -1 @@ -30,24 +27,12 @@ def __init__ (self, jdata): self.rcut_r_smth = jdata['rcut_smth'] else : self.rcut_r_smth = self.rcut_r - # fparam - self.numb_fparam = 0 - if j_have(jdata, 'numb_fparam') : - self.numb_fparam = jdata['numb_fparam'] - # type_map - self.type_map = [] - if j_have(jdata, 'type_map') : - self.numb_fparam = jdata['type_map'] # filter of smooth version - if j_have(jdata, 'coord_norm') : - self.coord_norm = jdata['coord_norm'] - else : - self.coord_norm = True - self.filter_neuron = j_must_have (jdata, 'filter_neuron') + self.filter_neuron = j_must_have (jdata, 'neuron') self.n_axis_neuron = j_must_have_d (jdata, 'axis_neuron', ['n_axis_neuron']) self.filter_resnet_dt = False - if j_have(jdata, 'filter_resnet_dt') : - self.filter_resnet_dt = jdata['filter_resnet_dt'] + if j_have(jdata, 'resnet_dt') : + self.filter_resnet_dt = jdata['resnet_dt'] # numb of neighbors and numb of descrptors self.nnei_a = np.cumsum(self.sel_a)[-1] self.nnei_r = np.cumsum(self.sel_r)[-1] diff --git a/source/train/DescrptSeR.py b/source/train/DescrptSeR.py index 18ca1f9b42..cdd219fc77 100644 --- a/source/train/DescrptSeR.py +++ b/source/train/DescrptSeR.py @@ -16,12 +16,9 @@ class DescrptSeR (): def __init__ (self, jdata): # descrpt config - self.use_smooth = False - self.sel_r = j_must_have (jdata, 'sel_r') + self.sel_r = j_must_have (jdata, 'sel') self.sel_a = [ 0 for ii in range(len(self.sel_r)) ] self.sel = self.sel_r - if j_have (jdata, 'sel_a') : - warnings.warn ('ignoring key sel_a in the json database and set sel_r to %s' % str(self.sel_a)) self.ntypes = len(self.sel_r) self.rcut = j_must_have (jdata, 'rcut') if j_have(jdata, 'rcut_smth') : @@ -29,14 +26,10 @@ def __init__ (self, jdata): else : self.rcut_smth = self.rcut # filter of smooth version - if j_have(jdata, 'coord_norm') : - self.coord_norm = jdata['coord_norm'] - else : - self.coord_norm = True - self.filter_neuron = j_must_have (jdata, 'filter_neuron') + self.filter_neuron = j_must_have (jdata, 'neuron') self.filter_resnet_dt = False - if j_have(jdata, 'filter_resnet_dt') : - self.filter_resnet_dt = jdata['filter_resnet_dt'] + if j_have(jdata, 'resnet_dt') : + self.filter_resnet_dt = jdata['resnet_dt'] # numb of neighbors and numb of descrptors self.nnei_a = np.cumsum(self.sel_a)[-1] self.nnei_r = np.cumsum(self.sel_r)[-1] diff --git a/source/train/EnerFitting.py b/source/train/EnerFitting.py index f8855e1998..5af7291804 100644 --- a/source/train/EnerFitting.py +++ b/source/train/EnerFitting.py @@ -20,13 +20,10 @@ def __init__ (self, jdata, descrpt): if j_have(jdata, 'numb_fparam') : self.numb_fparam = jdata['numb_fparam'] # network size - self.n_neuron = j_must_have_d (jdata, 'fitting_neuron', ['n_neuron']) + self.n_neuron = j_must_have_d (jdata, 'neuron', ['n_neuron']) self.resnet_dt = True if j_have(jdata, 'resnet_dt') : - warnings.warn("the key \"%s\" is deprecated, please use \"%s\" instead" % ('resnet_dt','fitting_resnet_dt')) self.resnet_dt = jdata['resnet_dt'] - if j_have(jdata, 'fitting_resnet_dt') : - self.resnet_dt = jdata['fitting_resnet_dt'] self.seed = None if j_have (jdata, 'seed') : diff --git a/source/train/Model.py b/source/train/Model.py index c51cbf4bee..e815986f1d 100644 --- a/source/train/Model.py +++ b/source/train/Model.py @@ -14,18 +14,13 @@ def __init__ (self, jdata, descrpt, fitting): self.descrpt = descrpt self.rcut = self.descrpt.get_rcut() self.ntypes = self.descrpt.get_ntypes() - # fparam - self.numb_fparam = 0 - if j_have(jdata, 'numb_fparam') : - self.numb_fparam = jdata['numb_fparam'] # type_map self.type_map = [] if j_have(jdata, 'type_map') : - self.numb_fparam = jdata['type_map'] + self.type_map = jdata['type_map'] # fitting self.fitting = fitting self.numb_fparam = self.fitting.get_numb_fparam() - # short-range tab if 'use_srtab' in jdata : self.srtab = TabInter(jdata['use_srtab']) @@ -35,11 +30,6 @@ def __init__ (self, jdata, descrpt, fitting): else : self.srtab = None - self.seed = None - if j_have (jdata, 'seed') : - self.seed = jdata['seed'] - self.useBN = False - def get_rcut (self) : return self.rcut diff --git a/source/train/Trainer.py b/source/train/Trainer.py index 0501fb29fc..5574d38f7f 100644 --- a/source/train/Trainer.py +++ b/source/train/Trainer.py @@ -83,25 +83,26 @@ def __init__(self, self._init_param(jdata) def _init_param(self, jdata): - # descrpt config - model_type = j_must_have(jdata, 'model_type') - if model_type == 'loc_frame': - model_param = j_must_have(jdata, 'model') - self.descrpt = DescrptLocFrame(model_param) - elif model_type == 'se_a' : - model_param = j_must_have(jdata, 'model') - self.descrpt = DescrptSeA(model_param) - elif model_type == 'se_r' : - model_param = j_must_have(jdata, 'model') - self.descrpt = DescrptSeR(model_param) + # model config + model_param = j_must_have(jdata, 'model') + descrpt_param = j_must_have(model_param, 'descriptor') + fitting_param = j_must_have(model_param, 'fitting_net') + # descriptor + descrpt_type = j_must_have(descrpt_param, 'type') + if descrpt_type == 'loc_frame': + self.descrpt = DescrptLocFrame(descrpt_param) + elif descrpt_type == 'se_a' : + self.descrpt = DescrptSeA(descrpt_param) + elif descrpt_type == 'se_r' : + self.descrpt = DescrptSeR(descrpt_param) # elif model_type == 'se_ar' : # model_param_a = j_must_have(jdata, 'model_a') # model_param_r = j_must_have(jdata, 'model_r') # self.model = ModelHyb(model_param_a, model_param_r) else : raise RuntimeError('unknow model type ' + model_type) - - self.fitting = EnerFitting(model_param, self.descrpt) + # fitting net + self.fitting = EnerFitting(fitting_param, self.descrpt) self.model = Model(model_param, self.descrpt, self.fitting) self.numb_test = j_must_have (jdata, 'numb_test') diff --git a/source/train/print_old_model.py b/source/train/print_old_model.py new file mode 100644 index 0000000000..eb7e7883ad --- /dev/null +++ b/source/train/print_old_model.py @@ -0,0 +1,84 @@ +import dpdata,os,sys,json +import numpy as np +import tensorflow as tf +from common import Data + +lib_path = os.path.dirname(os.path.realpath(__file__)) + ".." +sys.path.append (lib_path) + +from deepmd.RunOptions import RunOptions +from deepmd.DataSystem import DataSystem +from deepmd.Model import NNPModel +from deepmd.Model import LearingRate +from deepmd.common import j_must_have, j_must_have_d, j_have + +def gen_data() : + tmpdata = Data(rand_pert = 0.1, seed = 1) + sys = dpdata.LabeledSystem() + sys.data['coords'] = tmpdata.coord + sys.data['atom_types'] = tmpdata.atype + sys.data['cells'] = tmpdata.cell + nframes = tmpdata.nframes + natoms = tmpdata.natoms + print(sys.data['coords']) + sys.data['coords'] = sys.data['coords'].reshape([nframes,natoms,3]) + sys.data['cells'] = sys.data['cells'].reshape([nframes,3,3]) + sys.data['energies'] = np.zeros([nframes,1]) + sys.data['forces'] = np.zeros([nframes,natoms,3]) + sys.data['virials'] = [] + sys.to_deepmd_npy('system', prec=np.float64) + +def compute_efv(jfile): + fp = open (jfile, 'r') + jdata = json.load (fp) + run_opt = RunOptions(None) + systems = j_must_have(jdata, 'systems') + set_pfx = j_must_have(jdata, 'set_prefix') + batch_size = j_must_have(jdata, 'batch_size') + test_size = j_must_have(jdata, 'numb_test') + batch_size = 1 + test_size = 1 + stop_batch = j_must_have(jdata, 'stop_batch') + rcut = j_must_have (jdata, 'rcut') + + data = DataSystem(systems, set_pfx, batch_size, test_size, rcut, run_opt) + + tot_numb_batches = sum(data.get_nbatches()) + lr = LearingRate (jdata, tot_numb_batches) + + model = NNPModel (jdata, run_opt = run_opt) + model.build (data, lr) + + test_prop_c, \ + test_energy, test_force, test_virial, test_atom_ener, \ + test_coord, test_box, test_type, test_fparam, \ + natoms_vec, \ + default_mesh \ + = data.get_test () + feed_dict_test = {model.t_prop_c: test_prop_c, + model.t_energy: test_energy [:model.numb_test], + model.t_force: np.reshape(test_force [:model.numb_test, :], [-1]), + model.t_virial: np.reshape(test_virial [:model.numb_test, :], [-1]), + model.t_atom_ener: np.reshape(test_atom_ener[:model.numb_test, :], [-1]), + model.t_coord: np.reshape(test_coord [:model.numb_test, :], [-1]), + model.t_box: test_box [:model.numb_test, :], + model.t_type: np.reshape(test_type [:model.numb_test, :], [-1]), + model.t_natoms: natoms_vec, + model.t_mesh: default_mesh, + model.is_training: False} + + sess = tf.Session() + sess.run(tf.global_variables_initializer()) + [e, f, v] = sess.run([model.energy, model.force, model.virial], + feed_dict = feed_dict_test) + return e,f,v + +def _main() : + gen_data() + e,f,v = compute_efv('water_smth.json') + np.savetxt('e.out', e, delimiter=',') + np.savetxt('f.out', f, delimiter=',') + np.savetxt('v.out', v, delimiter=',') + + +_main() From ac812bab9e364b57f23521cefe815e6f1a9b772c Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 28 Jun 2019 09:57:58 +0800 Subject: [PATCH 17/33] rearrange examples --- examples/{ => water}/data/water/set.000/box.npy | Bin examples/{ => water}/data/water/set.000/coord.npy | Bin examples/{ => water}/data/water/set.000/energy.npy | Bin examples/{ => water}/data/water/set.000/force.npy | Bin examples/{ => water}/data/water/set.001/box.npy | Bin examples/{ => water}/data/water/set.001/coord.npy | Bin examples/{ => water}/data/water/set.001/energy.npy | Bin examples/{ => water}/data/water/set.001/force.npy | Bin examples/{ => water}/data/water/set.002/box.npy | Bin examples/{ => water}/data/water/set.002/coord.npy | Bin examples/{ => water}/data/water/set.002/energy.npy | Bin examples/{ => water}/data/water/set.002/force.npy | Bin examples/{ => water}/data/water/set.003/box.npy | Bin examples/{ => water}/data/water/set.003/coord.npy | Bin examples/{ => water}/data/water/set.003/energy.npy | Bin examples/{ => water}/data/water/set.003/force.npy | Bin examples/{ => water}/data/water/type.raw | 0 examples/{ => water}/ipi/water.json | 0 examples/{ => water}/lmp/.gitignore | 0 examples/{ => water}/lmp/in.lammps | 0 examples/{ => water}/lmp/water.lmp | 0 examples/{ => water}/train/.gitignore | 0 examples/{ => water}/train/water.json | 0 examples/{ => water}/train/water_se_a.json | 0 examples/{ => water}/train/water_se_ar.json | 0 examples/{ => water}/train/water_se_r.json | 0 26 files changed, 0 insertions(+), 0 deletions(-) rename examples/{ => water}/data/water/set.000/box.npy (100%) rename examples/{ => water}/data/water/set.000/coord.npy (100%) rename examples/{ => water}/data/water/set.000/energy.npy (100%) rename examples/{ => water}/data/water/set.000/force.npy (100%) rename examples/{ => water}/data/water/set.001/box.npy (100%) rename examples/{ => water}/data/water/set.001/coord.npy (100%) rename examples/{ => water}/data/water/set.001/energy.npy (100%) rename examples/{ => water}/data/water/set.001/force.npy (100%) rename examples/{ => water}/data/water/set.002/box.npy (100%) rename examples/{ => water}/data/water/set.002/coord.npy (100%) rename examples/{ => water}/data/water/set.002/energy.npy (100%) rename examples/{ => water}/data/water/set.002/force.npy (100%) rename examples/{ => water}/data/water/set.003/box.npy (100%) rename examples/{ => water}/data/water/set.003/coord.npy (100%) rename examples/{ => water}/data/water/set.003/energy.npy (100%) rename examples/{ => water}/data/water/set.003/force.npy (100%) rename examples/{ => water}/data/water/type.raw (100%) rename examples/{ => water}/ipi/water.json (100%) rename examples/{ => water}/lmp/.gitignore (100%) rename examples/{ => water}/lmp/in.lammps (100%) rename examples/{ => water}/lmp/water.lmp (100%) rename examples/{ => water}/train/.gitignore (100%) rename examples/{ => water}/train/water.json (100%) rename examples/{ => water}/train/water_se_a.json (100%) rename examples/{ => water}/train/water_se_ar.json (100%) rename examples/{ => water}/train/water_se_r.json (100%) diff --git a/examples/data/water/set.000/box.npy b/examples/water/data/water/set.000/box.npy similarity index 100% rename from examples/data/water/set.000/box.npy rename to examples/water/data/water/set.000/box.npy diff --git a/examples/data/water/set.000/coord.npy b/examples/water/data/water/set.000/coord.npy similarity index 100% rename from examples/data/water/set.000/coord.npy rename to examples/water/data/water/set.000/coord.npy diff --git a/examples/data/water/set.000/energy.npy b/examples/water/data/water/set.000/energy.npy similarity index 100% rename from examples/data/water/set.000/energy.npy rename to examples/water/data/water/set.000/energy.npy diff --git a/examples/data/water/set.000/force.npy b/examples/water/data/water/set.000/force.npy similarity index 100% rename from examples/data/water/set.000/force.npy rename to examples/water/data/water/set.000/force.npy diff --git a/examples/data/water/set.001/box.npy b/examples/water/data/water/set.001/box.npy similarity index 100% rename from examples/data/water/set.001/box.npy rename to examples/water/data/water/set.001/box.npy diff --git a/examples/data/water/set.001/coord.npy b/examples/water/data/water/set.001/coord.npy similarity index 100% rename from examples/data/water/set.001/coord.npy rename to examples/water/data/water/set.001/coord.npy diff --git a/examples/data/water/set.001/energy.npy b/examples/water/data/water/set.001/energy.npy similarity index 100% rename from examples/data/water/set.001/energy.npy rename to examples/water/data/water/set.001/energy.npy diff --git a/examples/data/water/set.001/force.npy b/examples/water/data/water/set.001/force.npy similarity index 100% rename from examples/data/water/set.001/force.npy rename to examples/water/data/water/set.001/force.npy diff --git a/examples/data/water/set.002/box.npy b/examples/water/data/water/set.002/box.npy similarity index 100% rename from examples/data/water/set.002/box.npy rename to examples/water/data/water/set.002/box.npy diff --git a/examples/data/water/set.002/coord.npy b/examples/water/data/water/set.002/coord.npy similarity index 100% rename from examples/data/water/set.002/coord.npy rename to examples/water/data/water/set.002/coord.npy diff --git a/examples/data/water/set.002/energy.npy b/examples/water/data/water/set.002/energy.npy similarity index 100% rename from examples/data/water/set.002/energy.npy rename to examples/water/data/water/set.002/energy.npy diff --git a/examples/data/water/set.002/force.npy b/examples/water/data/water/set.002/force.npy similarity index 100% rename from examples/data/water/set.002/force.npy rename to examples/water/data/water/set.002/force.npy diff --git a/examples/data/water/set.003/box.npy b/examples/water/data/water/set.003/box.npy similarity index 100% rename from examples/data/water/set.003/box.npy rename to examples/water/data/water/set.003/box.npy diff --git a/examples/data/water/set.003/coord.npy b/examples/water/data/water/set.003/coord.npy similarity index 100% rename from examples/data/water/set.003/coord.npy rename to examples/water/data/water/set.003/coord.npy diff --git a/examples/data/water/set.003/energy.npy b/examples/water/data/water/set.003/energy.npy similarity index 100% rename from examples/data/water/set.003/energy.npy rename to examples/water/data/water/set.003/energy.npy diff --git a/examples/data/water/set.003/force.npy b/examples/water/data/water/set.003/force.npy similarity index 100% rename from examples/data/water/set.003/force.npy rename to examples/water/data/water/set.003/force.npy diff --git a/examples/data/water/type.raw b/examples/water/data/water/type.raw similarity index 100% rename from examples/data/water/type.raw rename to examples/water/data/water/type.raw diff --git a/examples/ipi/water.json b/examples/water/ipi/water.json similarity index 100% rename from examples/ipi/water.json rename to examples/water/ipi/water.json diff --git a/examples/lmp/.gitignore b/examples/water/lmp/.gitignore similarity index 100% rename from examples/lmp/.gitignore rename to examples/water/lmp/.gitignore diff --git a/examples/lmp/in.lammps b/examples/water/lmp/in.lammps similarity index 100% rename from examples/lmp/in.lammps rename to examples/water/lmp/in.lammps diff --git a/examples/lmp/water.lmp b/examples/water/lmp/water.lmp similarity index 100% rename from examples/lmp/water.lmp rename to examples/water/lmp/water.lmp diff --git a/examples/train/.gitignore b/examples/water/train/.gitignore similarity index 100% rename from examples/train/.gitignore rename to examples/water/train/.gitignore diff --git a/examples/train/water.json b/examples/water/train/water.json similarity index 100% rename from examples/train/water.json rename to examples/water/train/water.json diff --git a/examples/train/water_se_a.json b/examples/water/train/water_se_a.json similarity index 100% rename from examples/train/water_se_a.json rename to examples/water/train/water_se_a.json diff --git a/examples/train/water_se_ar.json b/examples/water/train/water_se_ar.json similarity index 100% rename from examples/train/water_se_ar.json rename to examples/water/train/water_se_ar.json diff --git a/examples/train/water_se_r.json b/examples/water/train/water_se_r.json similarity index 100% rename from examples/train/water_se_r.json rename to examples/water/train/water_se_r.json From 01e97292bd31073a160905c092f19eced9d02490 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 28 Jun 2019 12:40:17 +0800 Subject: [PATCH 18/33] make short-range table work --- source/tests/test_model_se_a_srtab.py | 141 ++++++++++++++++++++++++++ source/tests/water_se_a_srtab.json | 59 +++++++++++ source/train/DescrptLocFrame.py | 3 + source/train/DescrptSeA.py | 11 +- source/train/DescrptSeR.py | 9 +- source/train/Model.py | 51 ++++++---- 6 files changed, 249 insertions(+), 25 deletions(-) create mode 100644 source/tests/test_model_se_a_srtab.py create mode 100644 source/tests/water_se_a_srtab.json diff --git a/source/tests/test_model_se_a_srtab.py b/source/tests/test_model_se_a_srtab.py new file mode 100644 index 0000000000..094b931fa1 --- /dev/null +++ b/source/tests/test_model_se_a_srtab.py @@ -0,0 +1,141 @@ +import dpdata,os,sys,json,unittest +import numpy as np +import tensorflow as tf +from common import Data + +lib_path = os.path.dirname(os.path.realpath(__file__)) + ".." +sys.path.append (lib_path) + +from deepmd.RunOptions import RunOptions +from deepmd.DataSystem import DataSystem +from deepmd.DescrptSeA import DescrptSeA +from deepmd.EnerFitting import EnerFitting +from deepmd.Model import Model +from deepmd.common import j_must_have, j_must_have_d, j_have + +global_ener_float_precision = tf.float64 +global_tf_float_precision = tf.float64 +global_np_float_precision = np.float64 + +def gen_data() : + tmpdata = Data(rand_pert = 0.1, seed = 1) + sys = dpdata.LabeledSystem() + sys.data['coords'] = tmpdata.coord + sys.data['atom_types'] = tmpdata.atype + sys.data['cells'] = tmpdata.cell + nframes = tmpdata.nframes + natoms = tmpdata.natoms + sys.data['coords'] = sys.data['coords'].reshape([nframes,natoms,3]) + sys.data['cells'] = sys.data['cells'].reshape([nframes,3,3]) + sys.data['energies'] = np.zeros([nframes,1]) + sys.data['forces'] = np.zeros([nframes,natoms,3]) + sys.data['virials'] = [] + sys.to_deepmd_npy('system', prec=np.float64) + +def _make_tab(ntype) : + xx = np.arange(0,9,0.001) + yy = 1000/(xx+.5)**6 + prt = xx + ninter = ntype * (ntype + 1) // 2 + for ii in range(ninter) : + prt = np.append(prt, yy) + prt = np.reshape(prt, [ninter+1, -1]) + np.savetxt('tab.xvg', prt.T) + +class TestModel(unittest.TestCase): + def setUp(self) : + gen_data() + + def test_model(self): + jfile = 'water_se_a.json' + with open(jfile) as fp: + jdata = json.load (fp) + run_opt = RunOptions(None) + systems = j_must_have(jdata, 'systems') + set_pfx = j_must_have(jdata, 'set_prefix') + batch_size = j_must_have(jdata, 'batch_size') + test_size = j_must_have(jdata, 'numb_test') + batch_size = 1 + test_size = 1 + stop_batch = j_must_have(jdata, 'stop_batch') + rcut = j_must_have (jdata['model']['descriptor'], 'rcut') + + data = DataSystem(systems, set_pfx, batch_size, test_size, rcut, run_opt = None) + + test_prop_c, \ + test_energy, test_force, test_virial, test_atom_ener, \ + test_coord, test_box, test_type, test_fparam, \ + natoms_vec, \ + default_mesh \ + = data.get_test () + numb_test = 1 + + bias_atom_e = data.compute_energy_shift() + + descrpt = DescrptSeA(jdata['model']['descriptor']) + fitting = EnerFitting(jdata['model']['fitting_net'], descrpt) + model = Model(jdata['model'], descrpt, fitting) + + davg, dstd = model.compute_dstats([test_coord], [test_box], [test_type], [natoms_vec], [default_mesh]) + + t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') + t_energy = tf.placeholder(global_ener_float_precision, [None], name='t_energy') + t_force = tf.placeholder(global_tf_float_precision, [None], name='t_force') + t_virial = tf.placeholder(global_tf_float_precision, [None], name='t_virial') + t_atom_ener = tf.placeholder(global_tf_float_precision, [None], name='t_atom_ener') + t_coord = tf.placeholder(global_tf_float_precision, [None], name='i_coord') + t_type = tf.placeholder(tf.int32, [None], name='i_type') + t_natoms = tf.placeholder(tf.int32, [model.ntypes+2], name='i_natoms') + t_box = tf.placeholder(global_tf_float_precision, [None, 9], name='i_box') + t_mesh = tf.placeholder(tf.int32, [None], name='i_mesh') + is_training = tf.placeholder(tf.bool) + t_fparam = None + + energy, force, virial, atom_ener, atom_virial \ + = model.build_interaction (t_coord, + t_type, + t_natoms, + t_box, + t_mesh, + t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "se_a", + reuse = False) + + feed_dict_test = {t_prop_c: test_prop_c, + t_energy: test_energy [:numb_test], + t_force: np.reshape(test_force [:numb_test, :], [-1]), + t_virial: np.reshape(test_virial [:numb_test, :], [-1]), + t_atom_ener: np.reshape(test_atom_ener[:numb_test, :], [-1]), + t_coord: np.reshape(test_coord [:numb_test, :], [-1]), + t_box: test_box [:numb_test, :], + t_type: np.reshape(test_type [:numb_test, :], [-1]), + t_natoms: natoms_vec, + t_mesh: default_mesh, + is_training: False} + + sess = tf.Session() + sess.run(tf.global_variables_initializer()) + [e, f, v] = sess.run([energy, force, virial], + feed_dict = feed_dict_test) + + e = e.reshape([-1]) + f = f.reshape([-1]) + v = v.reshape([-1]) + + refe = [6.135449167779321300e+01] + reff = [7.799691562262310585e-02,9.423098804815030483e-02,3.790560997388224204e-03,1.432522403799846578e-01,1.148392791403983204e-01,-1.321871172563671148e-02,-7.318966526325138000e-02,6.516069212737778116e-02,5.406418483320515412e-04,5.870713761026503247e-02,-1.605402669549013672e-01,-5.089516979826595386e-03,-2.554593467731766654e-01,3.092063507347833987e-02,1.510355029451411479e-02,4.869271842355533952e-02,-1.446113274345035005e-01,-1.126524434771078789e-03] + refv = [-6.076776685178300053e-01,1.103174323630009418e-01,1.984250991380156690e-02,1.103174323630009557e-01,-3.319759402259439551e-01,-6.007404107650986258e-03,1.984250991380157036e-02,-6.007404107650981921e-03,-1.200076017439753642e-03] + refe = np.reshape(refe, [-1]) + reff = np.reshape(reff, [-1]) + refv = np.reshape(refv, [-1]) + + places = 10 + for ii in range(e.size) : + self.assertAlmostEqual(e[ii], refe[ii], places = places) + for ii in range(f.size) : + self.assertAlmostEqual(f[ii], reff[ii], places = places) + for ii in range(v.size) : + self.assertAlmostEqual(v[ii], refv[ii], places = places) diff --git a/source/tests/water_se_a_srtab.json b/source/tests/water_se_a_srtab.json new file mode 100644 index 0000000000..84c920d3e7 --- /dev/null +++ b/source/tests/water_se_a_srtab.json @@ -0,0 +1,59 @@ +{ + "_comment": " model parameters", + "model" : { + "use_srtab": "tab.xvg", + "smin_alpha": 0.3, + "sw_rmin": 0.6, + "sw_rmax": 1.4, + "descriptor" :{ + "type": "se_a", + "sel": [46, 92], + "rcut_smth": 5.80, + "rcut": 6.00, + "neuron": [25, 50, 100], + "resnet_dt": false, + "axis_neuron": 16, + "seed": 1 + }, + "fitting_net" : { + "neuron": [240, 240, 240], + "resnet_dt": true, + "seed": 1 + } + }, + + + "_comment": " traing controls", + "systems": ["system"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 1, + "start_lr": 0.005, + "decay_steps": 5000, + "decay_rate": 0.95, + + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + + "seed": 1, + + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 1, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training": true, + "time_training": true, + "profiling": false, + "profiling_file": "timeline.json", + + "_comment": "that's all" +} + diff --git a/source/train/DescrptLocFrame.py b/source/train/DescrptLocFrame.py index feb148a587..56e7955174 100644 --- a/source/train/DescrptLocFrame.py +++ b/source/train/DescrptLocFrame.py @@ -42,6 +42,9 @@ def get_ntypes (self) : def get_dim_out (self) : return self.ndescrpt + def get_nlist (self) : + return self.nlist, self.rij, self.sel_a, self.sel_r + def compute_dstats (self, data_coord, data_box, diff --git a/source/train/DescrptSeA.py b/source/train/DescrptSeA.py index 8990d7090d..7feaa5a806 100644 --- a/source/train/DescrptSeA.py +++ b/source/train/DescrptSeA.py @@ -1,4 +1,4 @@ -import os,warnings +import os,sys,warnings import numpy as np import tensorflow as tf from deepmd.common import j_must_have, j_must_have_d, j_have @@ -55,6 +55,9 @@ def get_ntypes (self) : def get_dim_out (self) : return self.filter_neuron[-1] * self.n_axis_neuron + def get_nlist (self) : + return self.nlist, self.rij, self.sel_a, self.sel_r + def compute_dstats (self, data_coord, data_box, @@ -151,13 +154,15 @@ def build (self, sel_a = self.sel_a, sel_r = self.sel_r) - self.dout = self._pass_filter(self.descrpt, natoms, suffix = suffix, reuse = reuse) + self.descrpt_reshape = tf.reshape(self.descrpt, [-1, self.ndescrpt]) + + self.dout = self._pass_filter(self.descrpt_reshape, natoms, suffix = suffix, reuse = reuse) return self.dout def prod_force_virial(self, atom_ener, natoms) : - [net_deriv] = tf.gradients (atom_ener, self.descrpt) + [net_deriv] = tf.gradients (atom_ener, self.descrpt_reshape) net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) force \ = op_module.prod_force_norot (net_deriv_reshape, diff --git a/source/train/DescrptSeR.py b/source/train/DescrptSeR.py index cdd219fc77..416220a14c 100644 --- a/source/train/DescrptSeR.py +++ b/source/train/DescrptSeR.py @@ -52,6 +52,9 @@ def get_ntypes (self) : def get_dim_out (self) : return self.filter_neuron[-1] + def get_nlist (self) : + return self.nlist, self.rij, self.sel_a, self.sel_r + def compute_dstats (self, data_coord, data_box, @@ -133,13 +136,15 @@ def build (self, rcut_smth = self.rcut_smth, sel = self.sel_r) - self.dout = self._pass_filter(self.descrpt, natoms, suffix = suffix, reuse = reuse) + self.descrpt_reshape = tf.reshape(self.descrpt, [-1, self.ndescrpt]) + + self.dout = self._pass_filter(self.descrpt_reshape, natoms, suffix = suffix, reuse = reuse) return self.dout def prod_force_virial(self, atom_ener, natoms) : - [net_deriv] = tf.gradients (atom_ener, self.descrpt) + [net_deriv] = tf.gradients (atom_ener, self.descrpt_reshape) net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) force \ = op_module.prod_force_se_r (net_deriv_reshape, diff --git a/source/train/Model.py b/source/train/Model.py index e815986f1d..b286fae06a 100644 --- a/source/train/Model.py +++ b/source/train/Model.py @@ -1,6 +1,7 @@ -import os,warnings +import os,sys,warnings import numpy as np import tensorflow as tf +from deepmd.TabInter import TabInter from deepmd.common import j_must_have, j_must_have_d, j_have from deepmd.RunOptions import global_tf_float_precision @@ -9,6 +10,10 @@ from deepmd.RunOptions import global_cvt_2_tf_float from deepmd.RunOptions import global_cvt_2_ener_float +module_path = os.path.dirname(os.path.realpath(__file__)) + "/" +assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" +op_module = tf.load_op_library(module_path + "libop_abi.so") + class Model() : def __init__ (self, jdata, descrpt, fitting): self.descrpt = descrpt @@ -114,17 +119,23 @@ def build_interaction (self, coord = tf.reshape (coord_, [-1, natoms[1] * 3]) atype = tf.reshape (atype_, [-1, natoms[1]]) - descrpt = self.descrpt.build(coord_, - atype_, - natoms, - box, - mesh, - davg = davg, - dstd = dstd, - suffix = suffix, - reuse = reuse) - - atom_ener = self.fitting.build (descrpt, + dout \ + = self.descrpt.build(coord_, + atype_, + natoms, + box, + mesh, + davg = davg, + dstd = dstd, + suffix = suffix, + reuse = reuse) + + if self.srtab is not None : + nlist, rij, sel_a, sel_r = self.descrpt.get_nlist() + nnei_a = np.cumsum(sel_a)[-1] + nnei_r = np.cumsum(sel_r)[-1] + + atom_ener = self.fitting.build (dout, fparam, natoms, bias_atom_e = bias_atom_e, @@ -137,8 +148,8 @@ def build_interaction (self, rij, nlist, natoms, - sel_a = self.sel_a, - sel_r = self.sel_r, + sel_a = sel_a, + sel_r = sel_r, alpha = self.smin_alpha, rmin = self.sw_rmin, rmax = self.sw_rmax) @@ -154,8 +165,8 @@ def build_interaction (self, nlist, natoms, sw_lambda, - sel_a = self.sel_a, - sel_r = self.sel_r) + sel_a = sel_a, + sel_r = sel_r) energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, natoms[0]]) tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener @@ -175,8 +186,8 @@ def build_interaction (self, sw_deriv, nlist, natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) + n_a_sel = nnei_a, + n_r_sel = nnei_r) force = force + sw_force + tab_force force = tf.reshape (force, [-1, 3 * natoms[1]], name = "o_force"+suffix) @@ -188,8 +199,8 @@ def build_interaction (self, rij, nlist, natoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) + n_a_sel = nnei_a, + n_r_sel = nnei_r) atom_virial = atom_virial + sw_atom_virial + tab_atom_virial virial = virial + sw_virial \ + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, natoms[1], 9]), axis = 1) From d61d40509bf5652a7287366e903733195ff4ec1f Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 28 Jun 2019 15:06:12 +0800 Subject: [PATCH 19/33] bug fixing: fparam does not work with multiple atom types --- source/train/EnerFitting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/train/EnerFitting.py b/source/train/EnerFitting.py index 5af7291804..58b5483253 100644 --- a/source/train/EnerFitting.py +++ b/source/train/EnerFitting.py @@ -66,7 +66,7 @@ def build (self, layer = inputs_i if self.numb_fparam > 0 : ext_fparam = tf.reshape(fparam, [-1, self.numb_fparam]) - ext_fparam = tf.tile(ext_fparam, [1, natoms[0]]) + ext_fparam = tf.tile(ext_fparam, [1, natoms[2+type_i]]) ext_fparam = tf.reshape(ext_fparam, [-1, self.numb_fparam]) layer = tf.concat([layer, ext_fparam], axis = 1) for ii in range(0,len(self.n_neuron)) : From 0498fa0f52dffc158d64aff5cf7c3ff7b6770617 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 28 Jun 2019 15:06:45 +0800 Subject: [PATCH 20/33] bug fixing: srtab testing conficting namescope --- source/tests/test_model_se_a_srtab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tests/test_model_se_a_srtab.py b/source/tests/test_model_se_a_srtab.py index 094b931fa1..d14a81e693 100644 --- a/source/tests/test_model_se_a_srtab.py +++ b/source/tests/test_model_se_a_srtab.py @@ -101,7 +101,7 @@ def test_model(self): davg = davg, dstd = dstd, bias_atom_e = bias_atom_e, - suffix = "se_a", + suffix = "se_a_srtab", reuse = False) feed_dict_test = {t_prop_c: test_prop_c, From 0e432bd6374fd94e30a32ee3e20a3182fd5324ac Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 28 Jun 2019 15:07:06 +0800 Subject: [PATCH 21/33] add unittest for fparam test --- source/tests/common.py | 1 + source/tests/test_model_se_a_fparam.py | 132 +++++++++++++++++++++++++ source/tests/water_se_a_fparam.json | 56 +++++++++++ source/train/print_old_model.py | 5 + 4 files changed, 194 insertions(+) create mode 100644 source/tests/test_model_se_a_fparam.py create mode 100644 source/tests/water_se_a_fparam.json diff --git a/source/tests/common.py b/source/tests/common.py index e715327ef4..8e52b8a284 100644 --- a/source/tests/common.py +++ b/source/tests/common.py @@ -12,6 +12,7 @@ def __init__ (self, self.coord = np.array(coord) np.random.seed(seed) self.coord += rand_pert * np.random.random(self.coord.shape) + self.fparam = np.array([[0.1, 0.2]]) self.atype = np.array([0, 1, 1, 0, 1, 1], dtype = int) self.cell = 20 * np.eye(3) self.nframes = 1 diff --git a/source/tests/test_model_se_a_fparam.py b/source/tests/test_model_se_a_fparam.py new file mode 100644 index 0000000000..1ab94e3482 --- /dev/null +++ b/source/tests/test_model_se_a_fparam.py @@ -0,0 +1,132 @@ +import dpdata,os,sys,json,unittest +import numpy as np +import tensorflow as tf +from common import Data + +lib_path = os.path.dirname(os.path.realpath(__file__)) + ".." +sys.path.append (lib_path) + +from deepmd.RunOptions import RunOptions +from deepmd.DataSystem import DataSystem +from deepmd.DescrptSeA import DescrptSeA +from deepmd.EnerFitting import EnerFitting +from deepmd.Model import Model +from deepmd.common import j_must_have, j_must_have_d, j_have + +global_ener_float_precision = tf.float64 +global_tf_float_precision = tf.float64 +global_np_float_precision = np.float64 + +def gen_data() : + tmpdata = Data(rand_pert = 0.1, seed = 1) + sys = dpdata.LabeledSystem() + sys.data['coords'] = tmpdata.coord + sys.data['atom_types'] = tmpdata.atype + sys.data['cells'] = tmpdata.cell + nframes = tmpdata.nframes + natoms = tmpdata.natoms + sys.data['coords'] = sys.data['coords'].reshape([nframes,natoms,3]) + sys.data['cells'] = sys.data['cells'].reshape([nframes,3,3]) + sys.data['energies'] = np.zeros([nframes,1]) + sys.data['forces'] = np.zeros([nframes,natoms,3]) + sys.data['virials'] = [] + sys.to_deepmd_npy('system', prec=np.float64) + np.save('system/set.000/fparam.npy', tmpdata.fparam) + +class TestModel(unittest.TestCase): + def setUp(self) : + gen_data() + + def test_model(self): + jfile = 'water_se_a_fparam.json' + with open(jfile) as fp: + jdata = json.load (fp) + run_opt = RunOptions(None) + systems = j_must_have(jdata, 'systems') + set_pfx = j_must_have(jdata, 'set_prefix') + batch_size = j_must_have(jdata, 'batch_size') + test_size = j_must_have(jdata, 'numb_test') + batch_size = 1 + test_size = 1 + stop_batch = j_must_have(jdata, 'stop_batch') + rcut = j_must_have (jdata['model']['descriptor'], 'rcut') + + data = DataSystem(systems, set_pfx, batch_size, test_size, rcut, run_opt = None) + + test_prop_c, \ + test_energy, test_force, test_virial, test_atom_ener, \ + test_coord, test_box, test_type, test_fparam, \ + natoms_vec, \ + default_mesh \ + = data.get_test () + numb_test = 1 + + bias_atom_e = data.compute_energy_shift() + + descrpt = DescrptSeA(jdata['model']['descriptor']) + fitting = EnerFitting(jdata['model']['fitting_net'], descrpt) + model = Model(jdata['model'], descrpt, fitting) + + davg, dstd = model.compute_dstats([test_coord], [test_box], [test_type], [natoms_vec], [default_mesh]) + + t_prop_c = tf.placeholder(tf.float32, [4], name='t_prop_c') + t_energy = tf.placeholder(global_ener_float_precision, [None], name='t_energy') + t_force = tf.placeholder(global_tf_float_precision, [None], name='t_force') + t_virial = tf.placeholder(global_tf_float_precision, [None], name='t_virial') + t_atom_ener = tf.placeholder(global_tf_float_precision, [None], name='t_atom_ener') + t_coord = tf.placeholder(global_tf_float_precision, [None], name='i_coord') + t_type = tf.placeholder(tf.int32, [None], name='i_type') + t_natoms = tf.placeholder(tf.int32, [model.ntypes+2], name='i_natoms') + t_box = tf.placeholder(global_tf_float_precision, [None, 9], name='i_box') + t_mesh = tf.placeholder(tf.int32, [None], name='i_mesh') + t_fparam = tf.placeholder(global_tf_float_precision, [None], name='i_fparam') + is_training = tf.placeholder(tf.bool) + + energy, force, virial, atom_ener, atom_virial \ + = model.build_interaction (t_coord, + t_type, + t_natoms, + t_box, + t_mesh, + t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "se_a_fparam", + reuse = False) + + feed_dict_test = {t_prop_c: test_prop_c, + t_energy: test_energy [:numb_test], + t_force: np.reshape(test_force [:numb_test, :], [-1]), + t_virial: np.reshape(test_virial [:numb_test, :], [-1]), + t_atom_ener: np.reshape(test_atom_ener[:numb_test, :], [-1]), + t_coord: np.reshape(test_coord [:numb_test, :], [-1]), + t_box: test_box [:numb_test, :], + t_type: np.reshape(test_type [:numb_test, :], [-1]), + t_natoms: natoms_vec, + t_mesh: default_mesh, + t_fparam: np.reshape(test_fparam [:numb_test, :], [-1]), + is_training: False} + + sess = tf.Session() + sess.run(tf.global_variables_initializer()) + [e, f, v] = sess.run([energy, force, virial], + feed_dict = feed_dict_test) + + e = e.reshape([-1]) + f = f.reshape([-1]) + v = v.reshape([-1]) + refe = [6.135136929183754972e+01] + reff = [7.761477777656561328e-02,9.383013575207051205e-02,3.776776376267230399e-03,1.428268971463224069e-01,1.143858253900619654e-01,-1.318441687719179231e-02,-7.271897092708884403e-02,6.494907553857684479e-02,5.355599592111062821e-04,5.840910251709752199e-02,-1.599042555763417750e-01,-5.067165555590445389e-03,-2.546246315216804113e-01,3.073296814647456451e-02,1.505994759166155023e-02,4.849282500878367153e-02,-1.439937492508420736e-01,-1.120701494357654411e-03] + refv = [-6.054303146013112480e-01,1.097859194719944115e-01,1.977605183964963390e-02,1.097859194719943976e-01,-3.306167096812382966e-01,-5.978855662865613894e-03,1.977605183964964083e-02,-5.978855662865616497e-03,-1.196331922996723236e-03] + refe = np.reshape(refe, [-1]) + reff = np.reshape(reff, [-1]) + refv = np.reshape(refv, [-1]) + + places = 10 + for ii in range(e.size) : + self.assertAlmostEqual(e[ii], refe[ii], places = places) + for ii in range(f.size) : + self.assertAlmostEqual(f[ii], reff[ii], places = places) + for ii in range(v.size) : + self.assertAlmostEqual(v[ii], refv[ii], places = places) diff --git a/source/tests/water_se_a_fparam.json b/source/tests/water_se_a_fparam.json new file mode 100644 index 0000000000..b27ae4c467 --- /dev/null +++ b/source/tests/water_se_a_fparam.json @@ -0,0 +1,56 @@ +{ + "_comment": " model parameters", + "model" : { + "descriptor" :{ + "type": "se_a", + "sel": [46, 92], + "rcut_smth": 5.80, + "rcut": 6.00, + "neuron": [25, 50, 100], + "resnet_dt": false, + "axis_neuron": 16, + "seed": 1 + }, + "fitting_net" : { + "neuron": [240, 240, 240], + "resnet_dt": true, + "numb_fparam": 2, + "seed": 1 + } + }, + + + "_comment": " traing controls", + "systems": ["system"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 1, + "start_lr": 0.005, + "decay_steps": 5000, + "decay_rate": 0.95, + + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + + "seed": 1, + + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 1, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training": true, + "time_training": true, + "profiling": false, + "profiling_file": "timeline.json", + + "_comment": "that's all" +} + diff --git a/source/train/print_old_model.py b/source/train/print_old_model.py index eb7e7883ad..90ec52de1a 100644 --- a/source/train/print_old_model.py +++ b/source/train/print_old_model.py @@ -3,6 +3,8 @@ import tensorflow as tf from common import Data +# hash: b721960c9d5c61ee161f9e929c7d76f77673bc10 + lib_path = os.path.dirname(os.path.realpath(__file__)) + ".." sys.path.append (lib_path) @@ -27,6 +29,7 @@ def gen_data() : sys.data['forces'] = np.zeros([nframes,natoms,3]) sys.data['virials'] = [] sys.to_deepmd_npy('system', prec=np.float64) + np.save('system/set.000/fparam.npy', tmpdata.fparam) def compute_efv(jfile): fp = open (jfile, 'r') @@ -55,6 +58,7 @@ def compute_efv(jfile): natoms_vec, \ default_mesh \ = data.get_test () + feed_dict_test = {model.t_prop_c: test_prop_c, model.t_energy: test_energy [:model.numb_test], model.t_force: np.reshape(test_force [:model.numb_test, :], [-1]), @@ -65,6 +69,7 @@ def compute_efv(jfile): model.t_type: np.reshape(test_type [:model.numb_test, :], [-1]), model.t_natoms: natoms_vec, model.t_mesh: default_mesh, + model.t_fparam: np.reshape(test_fparam [:model.numb_test, :], [-1]), model.is_training: False} sess = tf.Session() From 55d736ad07aba4f9c6a5160cb0c4269f1e168968 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 28 Jun 2019 15:07:31 +0800 Subject: [PATCH 22/33] ignore build dirs --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 24a94d7f9d..28934b59eb 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,8 @@ CMakeCache.txt CMakeFiles *.bk +build +_skbuild +deepmd.egg-info +dist From dc3ce46018472d4bd8cea89ef014ee7081f853a8 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Wed, 3 Jul 2019 13:22:22 +0800 Subject: [PATCH 23/33] merge cherry-pick rm train/Test.py --- source/train/Test.py | 566 ------------------------------------------- 1 file changed, 566 deletions(-) delete mode 100644 source/train/Test.py diff --git a/source/train/Test.py b/source/train/Test.py deleted file mode 100644 index 2af6884d6e..0000000000 --- a/source/train/Test.py +++ /dev/null @@ -1,566 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -import time -import numpy as np -import glob -import tensorflow as tf -from TabInter import TabInter - -from tensorflow.python.framework import ops - -# load force module -module_path = os.path.dirname(os.path.realpath(__file__)) + "/" -assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" -op_module = tf.load_op_library(module_path + "libop_abi.so") - -# load grad of force module -sys.path.append (module_path ) -import _prod_force_grad -import _prod_virial_grad -import _soft_min_force_grad -import _soft_min_virial_grad - -class DataSets (object): - def __init__ (self, - set_prefix = "set", - hh = 1e-6, - seed = None) : - self.dirs = glob.glob (set_prefix + ".*") - self.dirs.sort() - self.test_dir = self.dirs[-1] - self.set_count = 0 - self.hh = hh - self.load_test_set (self.test_dir) - - def get_numb_set (self) : - return len (self.train_dirs) - - def stats_energy (self) : - eners = [] - for ii in self.dirs: - ei = np.load (ii + "/energy.npy") - eners.append (np.average(ei)) - return np.average (eners) - - def load_test_set (self, - set_name) : - start_time = time.time() - coord_test = np.load (set_name + "/coord.npy") - box_test = np.load (set_name + "/box.npy") - # dirty workaround, type in type.raw should be sorted - type_test = np.loadtxt (set_name + "/../type.raw") - natoms = type_test.shape[0] - idx = np.arange (natoms) - self.idx_map = np.lexsort ((idx, type_test)) - atom_type3 = np.array([type_test[ii//3] for ii in range (natoms * 3)]) - idx3 = np.arange (natoms * 3) - self.idx3_map = np.lexsort ((idx3, atom_type3)) - - self.coord_test0 = np.array([coord_test[0]]) - self.box_test0 = np.array([box_test[0]]) - self.type_test0 = np.array([type_test]) - self.coord_test0 = self.coord_test0[:, self.idx3_map] - self.type_test0 = self.type_test0[:, self.idx_map] - - self.coord_test = self.coord_test0 - self.box_test = self.box_test0 - self.type_test = self.type_test0 - - coord0 = np.copy (self.coord_test[0]) - self.natoms = self.type_test[0].shape[0] - for ii in range(self.natoms * 3) : - p_coord = np.copy (coord0) - n_coord = np.copy (coord0) - p_coord[ii] += self.hh - n_coord[ii] -= self.hh - self.coord_test = np.append(self.coord_test, p_coord) - self.coord_test = np.append(self.coord_test, n_coord) - self.box_test = np.append(self.box_test, box_test[0]) - self.box_test = np.append(self.box_test, box_test[0]) - self.coord_test = np.reshape(self.coord_test, [self.natoms*6+1, -1]) - self.box_test = np.reshape(self.box_test, [self.natoms*6+1, 9]) - - self.coord_test = np.array(self.coord_test) - self.box_test = np.array(self.box_test) - self.type_test = np.tile (self.type_test, (2 * self.natoms * 3 + 1, 1)) - # self.type_test = np.tile (self.type_test, (3, 1)) - - end_time = time.time() - - def get_test (self) : - return self.coord_test, self.box_test, self.type_test - - def get_test0 (self) : - return self.coord_test0, self.box_test0, self.type_test0 - - def get_test_box (self, - hh) : - coord0_, box0_, type0_ = self.get_test0() - coord0 = coord0_[0] - box0 = box0_[0] - type0 = type0_[0] - nc = np.array( [coord0, coord0*(1+hh), coord0*(1-hh)] ) - nb = np.array( [box0, box0*(1+hh), box0*(1-hh)] ) - nt = np.array( [type0, type0, type0] ) - for dd in range(3) : - tmpc = np.copy (coord0) - tmpb = np.copy (box0) - tmpc = np.reshape(tmpc, [-1, 3]) - tmpc [:,dd] *= (1+hh) - tmpc = np.reshape(tmpc, [-1]) - tmpb = np.reshape(tmpb, [-1, 3]) - tmpb [dd,:] *= (1+hh) - tmpb = np.reshape(tmpb, [-1]) - nc = np.append (nc, [tmpc], axis = 0) - nb = np.append (nb, [tmpb], axis = 0) - nt = np.append (nt, [type0], axis = 0) - tmpc = np.copy (coord0) - tmpb = np.copy (box0) - tmpc = np.reshape(tmpc, [-1, 3]) - tmpc [:,dd] *= (1-hh) - tmpc = np.reshape(tmpc, [-1]) - tmpb = np.reshape(tmpb, [-1, 3]) - tmpb [dd,:] *= (1-hh) - tmpb = np.reshape(tmpb, [-1]) - nc = np.append (nc, [tmpc], axis = 0) - nb = np.append (nb, [tmpb], axis = 0) - nt = np.append (nt, [type0], axis = 0) - return nc, nb, nt - - def get_natoms (self) : - ntype1 = np.sum (self.type_test0) - tmp = np.array([self.natoms, self.natoms, self.natoms - ntype1, ntype1]) - return tmp.astype(np.int32) - - def get_h (self) : - return self.hh - -class Model (object) : - def __init__ (self, - sess, - data, - comp = 0) : - self.sess = sess - self.natoms = data.get_natoms() - self.ntypes = len(self.natoms) - 2 - self.comp = comp - self.sel_a = [12,24] - self.sel_r = [12,24] - self.rcut_a = -1 - self.rcut_r = 3.45 - self.axis_rule = [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0] - self.nnei_a = np.cumsum(self.sel_a)[-1] - self.nnei_r = np.cumsum(self.sel_r)[-1] - self.nnei = self.nnei_a + self.nnei_r - self.ndescrpt_a = self.nnei_a * 4 - self.ndescrpt_r = self.nnei_r * 1 - self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r - davg = np.zeros ([self.ntypes, self.ndescrpt]) - dstd = np.ones ([self.ntypes, self.ndescrpt]) - self.t_avg = tf.constant(davg.astype(np.float64)) - self.t_std = tf.constant(dstd.astype(np.float64)) - self.default_mesh = np.zeros (6, dtype = np.int32) - self.default_mesh[3] = 2 - self.default_mesh[4] = 2 - self.default_mesh[5] = 2 - self.srtab = TabInter('tab.xvg') - self.smin_alpha = 0.3 - self.sw_rmin = 1 - self.sw_rmax = 3.45 - tab_info, tab_data = self.srtab.get() - self.tab_info = tf.get_variable('t_tab_info', - tab_info.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_info, dtype = tf.float64)) - self.tab_data = tf.get_variable('t_tab_data', - tab_data.shape, - dtype = tf.float64, - trainable = False, - initializer = tf.constant_initializer(tab_data, dtype = tf.float64)) - - def net (self, - inputs, - name, - reuse = False) : - with tf.variable_scope(name, reuse=reuse): - net_w = tf.get_variable ('net_w', - [self.ndescrpt], - tf.float64, - tf.constant_initializer (self.net_w_i)) - dot_v = tf.matmul (tf.reshape (inputs, [-1, self.ndescrpt]), - tf.reshape (net_w, [self.ndescrpt, 1])) - return tf.reshape (dot_v, [-1]) - - def comp_ef (self, - dcoord, - dbox, - dtype, - tnatoms, - name, - reuse = None) : - descrpt, descrpt_deriv, rij, nlist, axis \ - = op_module.descrpt (dcoord, - dtype, - tnatoms, - dbox, - tf.constant(self.default_mesh), - self.t_avg, - self.t_std, - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - sel_a = self.sel_a, - sel_r = self.sel_r, - axis_rule = self.axis_rule) - inputs_reshape = tf.reshape (descrpt, [-1, self.ndescrpt]) - atom_ener = self.net (inputs_reshape, name, reuse = reuse) - atom_ener_reshape = tf.reshape(atom_ener, [-1, self.natoms[0]]) - energy = tf.reduce_sum (atom_ener_reshape, axis = 1) - net_deriv_ = tf.gradients (atom_ener, inputs_reshape) - net_deriv = net_deriv_[0] - net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) - - force = op_module.prod_force (net_deriv_reshape, - descrpt_deriv, - nlist, - axis, - tnatoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - virial, atom_vir = op_module.prod_virial (net_deriv_reshape, - descrpt_deriv, - rij, - nlist, - axis, - tnatoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - return energy, force, virial - - def comp_interpl_ef (self, - dcoord, - dbox, - dtype, - tnatoms, - name, - reuse = None) : - descrpt, descrpt_deriv, rij, nlist, axis \ - = op_module.descrpt (dcoord, - dtype, - tnatoms, - dbox, - tf.constant(self.default_mesh), - self.t_avg, - self.t_std, - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - sel_a = self.sel_a, - sel_r = self.sel_r, - axis_rule = self.axis_rule) - inputs_reshape = tf.reshape (descrpt, [-1, self.ndescrpt]) - atom_ener = self.net (inputs_reshape, name, reuse = reuse) - - sw_lambda, sw_deriv \ - = op_module.soft_min_switch(dtype, - rij, - nlist, - tnatoms, - sel_a = self.sel_a, - sel_r = self.sel_r, - alpha = self.smin_alpha, - rmin = self.sw_rmin, - rmax = self.sw_rmax) - inv_sw_lambda = 1.0 - sw_lambda - tab_atom_ener, tab_force, tab_atom_virial \ - = op_module.tab_inter(self.tab_info, - self.tab_data, - dtype, - rij, - nlist, - tnatoms, - sw_lambda, - sel_a = self.sel_a, - sel_r = self.sel_r) - energy_diff = tab_atom_ener - tf.reshape(atom_ener, [-1, self.natoms[0]]) - tab_atom_ener = tf.reshape(sw_lambda, [-1]) * tf.reshape(tab_atom_ener, [-1]) - atom_ener = tf.reshape(inv_sw_lambda, [-1]) * atom_ener - energy_raw = tab_atom_ener + atom_ener - - energy_raw = tf.reshape(energy_raw, [-1, self.natoms[0]]) - energy = tf.reduce_sum (energy_raw, axis = 1) - - net_deriv_ = tf.gradients (atom_ener, inputs_reshape) - net_deriv = net_deriv_[0] - net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) - - force = op_module.prod_force (net_deriv_reshape, - descrpt_deriv, - nlist, - axis, - tnatoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - sw_force \ - = op_module.soft_min_force(energy_diff, - sw_deriv, - nlist, - tnatoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - force = force + sw_force + tab_force - virial, atom_vir = op_module.prod_virial (net_deriv_reshape, - descrpt_deriv, - rij, - nlist, - axis, - tnatoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - sw_virial, sw_atom_virial \ - = op_module.soft_min_virial (energy_diff, - sw_deriv, - rij, - nlist, - tnatoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - # atom_virial = atom_virial + sw_atom_virial + tab_atom_virial - virial = virial + sw_virial \ - + tf.reduce_sum(tf.reshape(tab_atom_virial, [-1, self.natoms[1], 9]), axis = 1) - - return energy, force, virial - - # mimic loss term of force - def comp_fl (self, - dcoord, - dbox, - dtype, - tnatoms, - name, - c_ef, - reuse = None) : - energy, force, virial = c_ef (dcoord, dbox, dtype, tnatoms, name, reuse) - with tf.variable_scope(name, reuse=True): - net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) - f_mag = tf.reduce_sum (tf.nn.tanh(force)) - f_mag_dw = tf.gradients (f_mag, net_w) - assert (len(f_mag_dw) == 1), "length of dw is wrong" - return f_mag, f_mag_dw[0] - - - # mimic loss term of virial - def comp_vl (self, - dcoord, - dbox, - dtype, - tnatoms, - name, - c_ef, - reuse = None) : - energy, force, virial = c_ef (dcoord, dbox, dtype, tnatoms, name, reuse) - with tf.variable_scope(name, reuse=True): - net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) - v_mag = tf.reduce_sum (virial) - v_mag_dw = tf.gradients (v_mag, net_w) - assert (len(v_mag_dw) == 1), "length of dw is wrong" - return v_mag, v_mag_dw[0] - - def make_place (self) : - self.coord = tf.placeholder(tf.float64, [None, self.natoms[0] * 3], name='t_coord') - self.box = tf.placeholder(tf.float64, [None, 9], name='t_box') - self.type = tf.placeholder(tf.int32, [None, self.natoms[0]], name = "t_type") - self.tnatoms = tf.placeholder(tf.int32, [None], name = "t_natoms") - - def make_feed_dict (self, - data ) : - dcoord, dbox, dtype = data.get_test () - return {self.coord: dcoord, - self.box: dbox, - self.type: dtype, - self.tnatoms: self.natoms, - } - - def make_feed_dict0 (self, - data ) : - dcoord, dbox, dtype = data.get_test0 () - return {self.coord: dcoord, - self.box: dbox, - self.type: dtype, - self.tnatoms: self.natoms, - } - - def test_force (self, - data, - c_ef) : - self.make_place () - feed_dict_test = self.make_feed_dict (data) - feed_dict_test0 = self.make_feed_dict0 (data) - - self.net_w_i = 1 * np.ones (self.ndescrpt) - t_energy, t_force, t_virial = c_ef (self.coord, self.box, self.type, self.tnatoms, name = "test_0") - self.sess.run (tf.global_variables_initializer()) - energy = self.sess.run (t_energy, feed_dict = feed_dict_test) - force = self.sess.run (t_force , feed_dict = feed_dict_test) - # virial = self.sess.run (t_virial , feed_dict = feed_dict_test) - - hh2 = data.get_h() * 2. - ndof = (len(energy) - 1) // 2 - absolut_e = [] - relativ_e = [] - for ii in range (ndof) : - idx0 = ii * 2 + 1 - idx1 = ii * 2 + 2 - # +hh -hh - num_force = - (energy[idx0] - energy[idx1]) / hh2 - ana_force = force[0][ii] - diff = np.abs(num_force - ana_force) - absolut_e.append (diff) - relativ_e.append (diff / np.abs(ana_force)) - print ("component %6u \t value %12.5e \t diff: %10.2e \t relat: %10.2e" % (ii, ana_force, diff, np.abs(diff/ana_force))) - - print ("max absolute %e" % np.max(absolut_e)) - print ("max relative %e" % np.max(relativ_e)) - - - def comp_vol (self, - box) : - return np.linalg.det (np.reshape(box, (3,3))) - - def test_virial (self, - data, - c_ef) : - hh = 1e-6 - - self.make_place () - dcoord, dbox, dtype = data.get_test_box (hh) - feed_dict_box = {self.coord: dcoord, - self.box: dbox, - self.type: dtype, - self.tnatoms: self.natoms, - } - - self.net_w_i = 1 * np.ones (self.ndescrpt) - - t_energy, t_force, t_virial = c_ef (self.coord, self.box, self.type, self.tnatoms, name = "test_0") - self.sess.run (tf.global_variables_initializer()) - virial = self.sess.run (t_virial , feed_dict = feed_dict_box) - energy = self.sess.run (t_energy , feed_dict = feed_dict_box) - - print ("printing virial") - ana_vir3 = (virial[0][0] + virial[0][4] + virial[0][8])/3. / self.comp_vol(dbox[0]) - num_vir3 = -(energy[1] - energy[2]) / (self.comp_vol(dbox[1]) - self.comp_vol(dbox[2])) - print ( "all-dir: ana %14.5e num %14.5e diff %.2e" % (ana_vir3, num_vir3, np.abs(ana_vir3 - num_vir3)) ) - vir_idx = [0, 4, 8] - ana_v = [] - num_v = [] - for dd in range (3) : - ana_v.append (virial[0][vir_idx[dd]] / self.comp_vol(dbox[0])) - idx = 2 * (dd+1) + 1 - num_v.append ( -(energy[idx] - energy[idx+1]) / (self.comp_vol(dbox[idx]) - self.comp_vol(dbox[idx+1])) ) - for dd in range (3) : - print ( "dir %d: ana %14.5e num %14.5e diff %.2e" % (dd, ana_v[dd], num_v[dd], np.abs(ana_v[dd] - num_v[dd])) ) - - - def test_dw (self, - data, - c_ef) : - self.make_place () - feed_dict_test0 = self.make_feed_dict0 (data) - - w0 = np.ones (self.ndescrpt) - self.net_w_i = np.copy(w0) - - t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_0", c_ef = c_ef) - self.sess.run (tf.global_variables_initializer()) - ll_0 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - dw_0 = self.sess.run (t_dw, feed_dict = feed_dict_test0) - - hh = 1e-4 - absolut_e = [] - relativ_e = [] - for ii in range (self.ndescrpt) : - self.net_w_i = np.copy (w0) - self.net_w_i[ii] += hh - t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+1), c_ef = c_ef) - self.sess.run (tf.global_variables_initializer()) - ll_1 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - self.net_w_i[ii] -= 2. * hh - t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+2), c_ef = c_ef) - self.sess.run (tf.global_variables_initializer()) - ll_2 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - num_v = (ll_1 - ll_2) / (2. * hh) - ana_v = dw_0[ii] - diff = np.abs (num_v - ana_v) - if (np.abs(ana_v) < 1e-10) : - diff_r = diff - else : - diff_r = diff / np.abs(ana_v) - print ("component %6u \t value %12.5e n_v %.12e \t diff: %10.2e \t relat: %10.2e" % (ii, ana_v, num_v, diff, diff_r)) - absolut_e.append (diff) - relativ_e.append (diff_r) - - print ("max absolute %e" % np.max(absolut_e)) - print ("max relative %e" % np.max(relativ_e)) - - def test_virial_dw (self, - data, - c_ef) : - self.make_place () - feed_dict_test0 = self.make_feed_dict0 (data) - - w0 = np.ones (self.ndescrpt) - self.net_w_i = np.copy(w0) - - t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_0", c_ef = c_ef) - self.sess.run (tf.global_variables_initializer()) - ll_0 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - dw_0 = self.sess.run (t_dw, feed_dict = feed_dict_test0) - - hh = 1e-4 - absolut_e = [] - relativ_e = [] - for ii in range (self.ndescrpt) : - self.net_w_i = np.copy (w0) - self.net_w_i[ii] += hh - t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+1), c_ef = c_ef) - self.sess.run (tf.global_variables_initializer()) - ll_1 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - self.net_w_i[ii] -= 2. * hh - t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+2), c_ef = c_ef) - self.sess.run (tf.global_variables_initializer()) - ll_2 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - num_v = (ll_1 - ll_2) / (2. * hh) - ana_v = dw_0[ii] - diff = np.abs (num_v - ana_v) - if (np.abs(ana_v) < 1e-10) : - diff_r = diff - else : - diff_r = diff / np.abs(ana_v) - print ("component %6u \t value %12.5e n_v %12.5e \t diff: %10.2e \t relat: %10.2e" % (ii, ana_v, num_v, diff, diff_r)) - absolut_e.append (diff) - relativ_e.append (diff_r) - - print ("max absolute %e" % np.max(absolut_e)) - print ("max relative %e" % np.max(relativ_e)) - - -def _main () : - data = DataSets (set_prefix = "set") - tf.reset_default_graph() - - with tf.Session() as sess: - md = Model (sess, data) - # ######################################## - # use md.comp_ef or md.comp_interpl_ef - # ######################################## - # md.test_force (data, md.comp_interpl_ef) - # md.test_virial (data, md.comp_interpl_ef) - # md.test_dw (data, md.comp_interpl_ef) - md.test_virial_dw (data, md.comp_interpl_ef) - - -if __name__ == '__main__': - _main() - From d616ab4986374e44fd5792a2c9a179aad9c3ce32 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Thu, 11 Jul 2019 17:03:15 +0800 Subject: [PATCH 24/33] fix bug in setup --- source/setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/setup.py b/source/setup.py index cd3fbfc3c7..c46299f320 100644 --- a/source/setup.py +++ b/source/setup.py @@ -17,11 +17,12 @@ install_requires=[] setup( - name="deepmd", + name="deepmd-kit", + setup_requires=['setuptools-git-version'], version_format='{tag}.dev{commitcount}+{gitsha}', author="Han Wang", author_email="wang_han@iapcm.ac.cn", - description="Manipulating DeePMD-kit, VASP and LAMMPS data formats", + description="A deep learning package for many-body potential energy representation and molecular dynamics", long_description=readme, long_description_content_type="text/markdown", url="https://github.com/deepmodeling/deepmd-kit", From 5d1b322da04a95792f24069a6fd8d97fd65c55db Mon Sep 17 00:00:00 2001 From: Han Wang Date: Thu, 11 Jul 2019 17:22:03 +0800 Subject: [PATCH 25/33] rename norot -> se_a --- source/op/CMakeLists.txt | 4 ++-- ..._force_norot_grad.py => _prod_force_se_a_grad.py} | 0 ...irial_norot_grad.py => _prod_virial_se_a_grad.py} | 0 source/op/{descrpt_norot.cc => descrpt_se_a.cc} | 0 .../op/{prod_force_norot.cc => prod_force_se_a.cc} | 0 ...d_force_norot_grad.cc => prod_force_se_a_grad.cc} | 0 .../op/{prod_virial_norot.cc => prod_virial_se_a.cc} | 0 ...virial_norot_grad.cc => prod_virial_se_a_grad.cc} | 0 source/tests/test_descrpt_smooth.py | 10 +++++----- source/tests/test_tab_nonsmth.py | 4 ++-- source/tests/test_tab_smooth.py | 10 +++++----- source/train/DescrptSeA.py | 8 ++++---- source/train/TestNorot.py | 12 ++++++------ source/train/Trainer.py | 4 ++-- 14 files changed, 26 insertions(+), 26 deletions(-) rename source/op/{_prod_force_norot_grad.py => _prod_force_se_a_grad.py} (100%) rename source/op/{_prod_virial_norot_grad.py => _prod_virial_se_a_grad.py} (100%) rename source/op/{descrpt_norot.cc => descrpt_se_a.cc} (100%) rename source/op/{prod_force_norot.cc => prod_force_se_a.cc} (100%) rename source/op/{prod_force_norot_grad.cc => prod_force_se_a_grad.cc} (100%) rename source/op/{prod_virial_norot.cc => prod_virial_se_a.cc} (100%) rename source/op/{prod_virial_norot_grad.cc => prod_virial_se_a_grad.cc} (100%) diff --git a/source/op/CMakeLists.txt b/source/op/CMakeLists.txt index 269c0350fe..be8a191aba 100644 --- a/source/op/CMakeLists.txt +++ b/source/op/CMakeLists.txt @@ -3,8 +3,8 @@ set(OP_LIB ${PROJECT_SOURCE_DIR}/lib/src/SimulationRegion.cpp ${PROJECT_SOURCE_DIR}/lib/src/NeighborList.cpp) set (OP_CXX_FLAG -D_GLIBCXX_USE_CXX11_ABI=${OP_ABI} ) -file(GLOB OP_SRC prod_force.cc prod_virial.cc descrpt.cc descrpt_norot.cc descrpt_se_r.cc tab_inter.cc prod_force_norot.cc prod_virial_norot.cc prod_force_se_r.cc prod_virial_se_r.cc soft_min.cc soft_min_force.cc soft_min_virial.cc ) -file(GLOB OP_GRADS_SRC prod_force_grad.cc prod_force_norot_grad.cc prod_force_se_r_grad.cc prod_virial_grad.cc prod_virial_norot_grad.cc prod_virial_se_r_grad.cc soft_min_force_grad.cc soft_min_virial_grad.cc ) +file(GLOB OP_SRC prod_force.cc prod_virial.cc descrpt.cc descrpt_se_a.cc descrpt_se_r.cc tab_inter.cc prod_force_se_a.cc prod_virial_se_a.cc prod_force_se_r.cc prod_virial_se_r.cc soft_min.cc soft_min_force.cc soft_min_virial.cc ) +file(GLOB OP_GRADS_SRC prod_force_grad.cc prod_force_se_a_grad.cc prod_force_se_r_grad.cc prod_virial_grad.cc prod_virial_se_a_grad.cc prod_virial_se_r_grad.cc soft_min_force_grad.cc soft_min_virial_grad.cc ) file(GLOB OP_PY *.py) if (BUILD_CPP_IF) diff --git a/source/op/_prod_force_norot_grad.py b/source/op/_prod_force_se_a_grad.py similarity index 100% rename from source/op/_prod_force_norot_grad.py rename to source/op/_prod_force_se_a_grad.py diff --git a/source/op/_prod_virial_norot_grad.py b/source/op/_prod_virial_se_a_grad.py similarity index 100% rename from source/op/_prod_virial_norot_grad.py rename to source/op/_prod_virial_se_a_grad.py diff --git a/source/op/descrpt_norot.cc b/source/op/descrpt_se_a.cc similarity index 100% rename from source/op/descrpt_norot.cc rename to source/op/descrpt_se_a.cc diff --git a/source/op/prod_force_norot.cc b/source/op/prod_force_se_a.cc similarity index 100% rename from source/op/prod_force_norot.cc rename to source/op/prod_force_se_a.cc diff --git a/source/op/prod_force_norot_grad.cc b/source/op/prod_force_se_a_grad.cc similarity index 100% rename from source/op/prod_force_norot_grad.cc rename to source/op/prod_force_se_a_grad.cc diff --git a/source/op/prod_virial_norot.cc b/source/op/prod_virial_se_a.cc similarity index 100% rename from source/op/prod_virial_norot.cc rename to source/op/prod_virial_se_a.cc diff --git a/source/op/prod_virial_norot_grad.cc b/source/op/prod_virial_se_a_grad.cc similarity index 100% rename from source/op/prod_virial_norot_grad.cc rename to source/op/prod_virial_se_a_grad.cc diff --git a/source/tests/test_descrpt_smooth.py b/source/tests/test_descrpt_smooth.py index c6a46bdcef..633deaf7cd 100644 --- a/source/tests/test_descrpt_smooth.py +++ b/source/tests/test_descrpt_smooth.py @@ -10,8 +10,8 @@ sys.path.append (module_path) import _prod_force_grad import _prod_virial_grad -import _prod_force_norot_grad -import _prod_virial_norot_grad +import _prod_force_se_a_grad +import _prod_virial_se_a_grad import _soft_min_force_grad import _soft_min_virial_grad @@ -76,7 +76,7 @@ def comp_ef (self, name, reuse = None) : descrpt, descrpt_deriv, rij, nlist \ - = op_module.descrpt_norot (dcoord, + = op_module.descrpt_se_a (dcoord, dtype, tnatoms, dbox, @@ -96,13 +96,13 @@ def comp_ef (self, net_deriv = net_deriv_[0] net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) - force = op_module.prod_force_norot (net_deriv_reshape, + force = op_module.prod_force_se_a (net_deriv_reshape, descrpt_deriv, nlist, tnatoms, n_a_sel = self.nnei_a, n_r_sel = self.nnei_r) - virial, atom_vir = op_module.prod_virial_norot (net_deriv_reshape, + virial, atom_vir = op_module.prod_virial_se_a (net_deriv_reshape, descrpt_deriv, rij, nlist, diff --git a/source/tests/test_tab_nonsmth.py b/source/tests/test_tab_nonsmth.py index d1e3f153d0..00e1e5d9e4 100644 --- a/source/tests/test_tab_nonsmth.py +++ b/source/tests/test_tab_nonsmth.py @@ -10,8 +10,8 @@ sys.path.append (module_path) import _prod_force_grad import _prod_virial_grad -import _prod_force_norot_grad -import _prod_virial_norot_grad +import _prod_force_se_a_grad +import _prod_virial_se_a_grad import _soft_min_force_grad import _soft_min_virial_grad from TabInter import TabInter diff --git a/source/tests/test_tab_smooth.py b/source/tests/test_tab_smooth.py index 029aebf602..d3a8ff4750 100644 --- a/source/tests/test_tab_smooth.py +++ b/source/tests/test_tab_smooth.py @@ -10,8 +10,8 @@ sys.path.append (module_path) import _prod_force_grad import _prod_virial_grad -import _prod_force_norot_grad -import _prod_virial_norot_grad +import _prod_force_se_a_grad +import _prod_virial_se_a_grad import _soft_min_force_grad import _soft_min_virial_grad from TabInter import TabInter @@ -67,7 +67,7 @@ def comp_ef (self, name, reuse = None) : descrpt, descrpt_deriv, rij, nlist \ - = op_module.descrpt_norot (dcoord, + = op_module.descrpt_se_a (dcoord, dtype, tnatoms, dbox, @@ -115,7 +115,7 @@ def comp_ef (self, net_deriv = net_deriv_[0] net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) - force = op_module.prod_force_norot (net_deriv_reshape, + force = op_module.prod_force_se_a (net_deriv_reshape, descrpt_deriv, nlist, tnatoms, @@ -129,7 +129,7 @@ def comp_ef (self, n_a_sel = self.nnei_a, n_r_sel = self.nnei_r) force = force + sw_force + tab_force - virial, atom_vir = op_module.prod_virial_norot (net_deriv_reshape, + virial, atom_vir = op_module.prod_virial_se_a (net_deriv_reshape, descrpt_deriv, rij, nlist, diff --git a/source/train/DescrptSeA.py b/source/train/DescrptSeA.py index 7feaa5a806..5683aa0cc7 100644 --- a/source/train/DescrptSeA.py +++ b/source/train/DescrptSeA.py @@ -141,7 +141,7 @@ def build (self, atype = tf.reshape (atype_, [-1, natoms[1]]) self.descrpt, self.descrpt_deriv, self.rij, self.nlist \ - = op_module.descrpt_norot (coord, + = op_module.descrpt_se_a (coord, atype, natoms, box, @@ -165,14 +165,14 @@ def prod_force_virial(self, atom_ener, natoms) : [net_deriv] = tf.gradients (atom_ener, self.descrpt_reshape) net_deriv_reshape = tf.reshape (net_deriv, [-1, natoms[0] * self.ndescrpt]) force \ - = op_module.prod_force_norot (net_deriv_reshape, + = op_module.prod_force_se_a (net_deriv_reshape, self.descrpt_deriv, self.nlist, natoms, n_a_sel = self.nnei_a, n_r_sel = self.nnei_r) virial, atom_virial \ - = op_module.prod_virial_norot (net_deriv_reshape, + = op_module.prod_virial_se_a (net_deriv_reshape, self.descrpt_deriv, self.rij, self.nlist, @@ -216,7 +216,7 @@ def _compute_dstats_sys_smth (self, sub_graph = tf.Graph() with sub_graph.as_default(): descrpt, descrpt_deriv, rij, nlist \ - = op_module.descrpt_norot (tf.constant(data_coord), + = op_module.descrpt_se_a (tf.constant(data_coord), tf.constant(data_atype), tf.constant(natoms_vec, dtype = tf.int32), tf.constant(data_box), diff --git a/source/train/TestNorot.py b/source/train/TestNorot.py index c45ccb05f7..449adc8258 100644 --- a/source/train/TestNorot.py +++ b/source/train/TestNorot.py @@ -16,8 +16,8 @@ # load grad of force module sys.path.append (module_path ) -import _prod_force_norot_grad -import _prod_virial_norot_grad +import _prod_force_se_a_grad +import _prod_virial_se_a_grad class DataSets (object): def __init__ (self, @@ -173,7 +173,7 @@ def comp_descrpt (self, tnatoms, name) : descrpt, descrpt_deriv, rij, nlist, \ - = op_module.descrpt_norot (dcoord, + = op_module.descrpt_se_a (dcoord, dtype, tnatoms, dbox, @@ -195,7 +195,7 @@ def comp_ef (self, name, reuse = None) : descrpt, descrpt_deriv, rij, nlist, \ - = op_module.descrpt_norot (dcoord, + = op_module.descrpt_se_a (dcoord, dtype, tnatoms, dbox, @@ -215,13 +215,13 @@ def comp_ef (self, net_deriv = net_deriv_[0] net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) - force = op_module.prod_force_norot (net_deriv_reshape, + force = op_module.prod_force_se_a (net_deriv_reshape, descrpt_deriv, nlist, tnatoms, n_a_sel = self.nnei_a, n_r_sel = self.nnei_r) - virial, atom_vir = op_module.prod_virial_norot (net_deriv_reshape, + virial, atom_vir = op_module.prod_virial_se_a (net_deriv_reshape, descrpt_deriv, rij, nlist, diff --git a/source/train/Trainer.py b/source/train/Trainer.py index 5574d38f7f..72e4db06a7 100644 --- a/source/train/Trainer.py +++ b/source/train/Trainer.py @@ -33,8 +33,8 @@ sys.path.append (module_path ) import deepmd._prod_force_grad import deepmd._prod_virial_grad -import deepmd._prod_force_norot_grad -import deepmd._prod_virial_norot_grad +import deepmd._prod_force_se_a_grad +import deepmd._prod_virial_se_a_grad import deepmd._prod_force_se_r_grad import deepmd._prod_virial_se_r_grad import deepmd._soft_min_force_grad From 7c6c8193e164bf7f5209b45fe0948b2eb8e2cb55 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Thu, 11 Jul 2019 20:48:52 +0800 Subject: [PATCH 26/33] change name norot -> se_a --- source/lib/include/ComputeDescriptor.h | 4 +-- source/op/_prod_force_se_a_grad.py | 6 ++--- source/op/_prod_force_se_r_grad.py | 2 +- source/op/_prod_virial_se_a_grad.py | 6 ++--- source/op/_prod_virial_se_r_grad.py | 2 +- source/op/descrpt_se_a.cc | 36 +++++++++++++------------- source/op/prod_force_se_a.cc | 10 +++---- source/op/prod_force_se_a_grad.cc | 10 +++---- source/op/prod_virial_se_a.cc | 10 +++---- source/op/prod_virial_se_a_grad.cc | 10 +++---- 10 files changed, 48 insertions(+), 48 deletions(-) diff --git a/source/lib/include/ComputeDescriptor.h b/source/lib/include/ComputeDescriptor.h index 737702fc39..394be03f83 100644 --- a/source/lib/include/ComputeDescriptor.h +++ b/source/lib/include/ComputeDescriptor.h @@ -68,7 +68,7 @@ void compute_descriptor (vector & descrpt_a, const int axis1_idx); inline -void compute_descriptor_norot (vector & descrpt_a, +void compute_descriptor_se_a (vector & descrpt_a, vector & descrpt_a_deriv, vector & rij_a, const vector & posi, @@ -930,7 +930,7 @@ spline5_switch (double & vv, // output deriv size: n_sel_a_nei x 4 x 12 // (1./rr, cos_theta, cos_phi, sin_phi) x 4 x (x, y, z) -void compute_descriptor_norot (vector & descrpt_a, +void compute_descriptor_se_a (vector & descrpt_a, vector & descrpt_a_deriv, vector & rij_a, const vector & posi, diff --git a/source/op/_prod_force_se_a_grad.py b/source/op/_prod_force_se_a_grad.py index 0370b89b99..14e0b5556a 100644 --- a/source/op/_prod_force_se_a_grad.py +++ b/source/op/_prod_force_se_a_grad.py @@ -14,9 +14,9 @@ assert (os.path.isfile(module_file)), 'module op_grads does not exist' op_grads_module = tf.load_op_library(module_file) -@ops.RegisterGradient("ProdForceNorot") -def _prod_force_norot_grad_cc (op, grad): - net_grad = op_grads_module.prod_force_norot_grad (grad, +@ops.RegisterGradient("ProdForceSeA") +def _prod_force_se_a_grad_cc (op, grad): + net_grad = op_grads_module.prod_force_se_a_grad (grad, op.inputs[0], op.inputs[1], op.inputs[2], diff --git a/source/op/_prod_force_se_r_grad.py b/source/op/_prod_force_se_r_grad.py index f6b5b7d6df..be96ee7c4f 100644 --- a/source/op/_prod_force_se_r_grad.py +++ b/source/op/_prod_force_se_r_grad.py @@ -15,7 +15,7 @@ op_grads_module = tf.load_op_library(module_file) @ops.RegisterGradient("ProdForceSeR") -def _prod_force_norot_grad_cc (op, grad): +def _prod_force_se_a_grad_cc (op, grad): net_grad = op_grads_module.prod_force_se_r_grad (grad, op.inputs[0], op.inputs[1], diff --git a/source/op/_prod_virial_se_a_grad.py b/source/op/_prod_virial_se_a_grad.py index 441757434b..fb4d7688de 100644 --- a/source/op/_prod_virial_se_a_grad.py +++ b/source/op/_prod_virial_se_a_grad.py @@ -14,9 +14,9 @@ assert (os.path.isfile(module_file)), 'module op_grads does not exist' op_grads_module = tf.load_op_library(module_file) -@ops.RegisterGradient("ProdVirialNorot") -def _prod_virial_norot_grad_cc (op, grad, grad_atom): - net_grad = op_grads_module.prod_virial_norot_grad (grad, +@ops.RegisterGradient("ProdVirialSeA") +def _prod_virial_se_a_grad_cc (op, grad, grad_atom): + net_grad = op_grads_module.prod_virial_se_a_grad (grad, op.inputs[0], op.inputs[1], op.inputs[2], diff --git a/source/op/_prod_virial_se_r_grad.py b/source/op/_prod_virial_se_r_grad.py index 7c03ccbd63..15b3f4556c 100644 --- a/source/op/_prod_virial_se_r_grad.py +++ b/source/op/_prod_virial_se_r_grad.py @@ -15,7 +15,7 @@ op_grads_module = tf.load_op_library(module_file) @ops.RegisterGradient("ProdVirialSeR") -def _prod_virial_norot_grad_cc (op, grad, grad_atom): +def _prod_virial_se_a_grad_cc (op, grad, grad_atom): net_grad = op_grads_module.prod_virial_se_r_grad (grad, op.inputs[0], op.inputs[1], diff --git a/source/op/descrpt_se_a.cc b/source/op/descrpt_se_a.cc index 42031c2557..1764191b5a 100644 --- a/source/op/descrpt_se_a.cc +++ b/source/op/descrpt_se_a.cc @@ -19,7 +19,7 @@ typedef float VALUETYPE ; #endif #ifdef HIGH_PREC -REGISTER_OP("DescrptNorot") +REGISTER_OP("DescrptSeA") .Input("coord: double") .Input("type: int32") .Input("natoms: int32") @@ -37,7 +37,7 @@ REGISTER_OP("DescrptNorot") .Output("rij: double") .Output("nlist: int32"); #else -REGISTER_OP("DescrptNorot") +REGISTER_OP("DescrptSeA") .Input("coord: float") .Input("type: int32") .Input("natoms: int32") @@ -56,9 +56,9 @@ REGISTER_OP("DescrptNorot") .Output("nlist: int32"); #endif -class DescrptNorotOp : public OpKernel { +class DescrptSeAOp : public OpKernel { public: - explicit DescrptNorotOp(OpKernelConstruction* context) : OpKernel(context) { + explicit DescrptSeAOp(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("rcut_a", &rcut_a)); OP_REQUIRES_OK(context, context->GetAttr("rcut_r", &rcut_r)); OP_REQUIRES_OK(context, context->GetAttr("rcut_r_smth", &rcut_r_smth)); @@ -291,19 +291,19 @@ class DescrptNorotOp : public OpKernel { vector d_descrpt_r_deriv; vector d_rij_a; vector d_rij_r; - compute_descriptor_norot (d_descrpt_a, - d_descrpt_a_deriv, - d_rij_a, - d_coord3, - ntypes, - d_type, - region, - b_pbc, - ii, - fmt_nlist_a, - sec_a, - rcut_r_smth, - rcut_r); + compute_descriptor_se_a (d_descrpt_a, + d_descrpt_a_deriv, + d_rij_a, + d_coord3, + ntypes, + d_type, + region, + b_pbc, + ii, + fmt_nlist_a, + sec_a, + rcut_r_smth, + rcut_r); // check sizes assert (d_descrpt_a.size() == ndescrpt_a); @@ -395,5 +395,5 @@ class DescrptNorotOp : public OpKernel { } }; -REGISTER_KERNEL_BUILDER(Name("DescrptNorot").Device(DEVICE_CPU), DescrptNorotOp); +REGISTER_KERNEL_BUILDER(Name("DescrptSeA").Device(DEVICE_CPU), DescrptSeAOp); diff --git a/source/op/prod_force_se_a.cc b/source/op/prod_force_se_a.cc index 6f4d45e6e3..af0e712492 100644 --- a/source/op/prod_force_se_a.cc +++ b/source/op/prod_force_se_a.cc @@ -13,7 +13,7 @@ typedef float VALUETYPE; #endif #ifdef HIGH_PREC -REGISTER_OP("ProdForceNorot") +REGISTER_OP("ProdForceSeA") .Input("net_deriv: double") .Input("in_deriv: double") .Input("nlist: int32") @@ -22,7 +22,7 @@ REGISTER_OP("ProdForceNorot") .Attr("n_r_sel: int") .Output("force: double"); #else -REGISTER_OP("ProdForceNorot") +REGISTER_OP("ProdForceSeA") .Input("net_deriv: float") .Input("in_deriv: float") .Input("nlist: int32") @@ -34,9 +34,9 @@ REGISTER_OP("ProdForceNorot") using namespace tensorflow; -class ProdForceNorotOp : public OpKernel { +class ProdForceSeAOp : public OpKernel { public: - explicit ProdForceNorotOp(OpKernelConstruction* context) : OpKernel(context) { + explicit ProdForceSeAOp(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("n_a_sel", &n_a_sel)); OP_REQUIRES_OK(context, context->GetAttr("n_r_sel", &n_r_sel)); n_a_shift = n_a_sel * 4; @@ -155,7 +155,7 @@ class ProdForceNorotOp : public OpKernel { } }; -REGISTER_KERNEL_BUILDER(Name("ProdForceNorot").Device(DEVICE_CPU), ProdForceNorotOp); +REGISTER_KERNEL_BUILDER(Name("ProdForceSeA").Device(DEVICE_CPU), ProdForceSeAOp); diff --git a/source/op/prod_force_se_a_grad.cc b/source/op/prod_force_se_a_grad.cc index b640359908..eda965974a 100644 --- a/source/op/prod_force_se_a_grad.cc +++ b/source/op/prod_force_se_a_grad.cc @@ -13,7 +13,7 @@ typedef float VALUETYPE; #endif #ifdef HIGH_PREC -REGISTER_OP("ProdForceNorotGrad") +REGISTER_OP("ProdForceSeAGrad") .Input("grad: double") .Input("net_deriv: double") .Input("in_deriv: double") @@ -23,7 +23,7 @@ REGISTER_OP("ProdForceNorotGrad") .Attr("n_r_sel: int") .Output("grad_net: double"); #else -REGISTER_OP("ProdForceNorotGrad") +REGISTER_OP("ProdForceSeAGrad") .Input("grad: float") .Input("net_deriv: float") .Input("in_deriv: float") @@ -34,10 +34,10 @@ REGISTER_OP("ProdForceNorotGrad") .Output("grad_net: float"); #endif -class ProdForceNorotGradOp : public OpKernel +class ProdForceSeAGradOp : public OpKernel { public: - explicit ProdForceNorotGradOp(OpKernelConstruction* context) : OpKernel(context) { + explicit ProdForceSeAGradOp(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("n_a_sel", &n_a_sel)); OP_REQUIRES_OK(context, context->GetAttr("n_r_sel", &n_r_sel)); n_a_shift = n_a_sel * 4; @@ -158,4 +158,4 @@ class ProdForceNorotGradOp : public OpKernel } }; -REGISTER_KERNEL_BUILDER(Name("ProdForceNorotGrad").Device(DEVICE_CPU), ProdForceNorotGradOp); +REGISTER_KERNEL_BUILDER(Name("ProdForceSeAGrad").Device(DEVICE_CPU), ProdForceSeAGradOp); diff --git a/source/op/prod_virial_se_a.cc b/source/op/prod_virial_se_a.cc index fb07127ae6..89077750af 100644 --- a/source/op/prod_virial_se_a.cc +++ b/source/op/prod_virial_se_a.cc @@ -13,7 +13,7 @@ typedef float VALUETYPE; #endif #ifdef HIGH_PREC -REGISTER_OP("ProdVirialNorot") +REGISTER_OP("ProdVirialSeA") .Input("net_deriv: double") .Input("in_deriv: double") .Input("rij: double") @@ -25,7 +25,7 @@ REGISTER_OP("ProdVirialNorot") .Output("atom_virial: double") ; #else -REGISTER_OP("ProdVirialNorot") +REGISTER_OP("ProdVirialSeA") .Input("net_deriv: float") .Input("in_deriv: float") .Input("rij: float") @@ -40,9 +40,9 @@ REGISTER_OP("ProdVirialNorot") using namespace tensorflow; -class ProdVirialNorotOp : public OpKernel { +class ProdVirialSeAOp : public OpKernel { public: - explicit ProdVirialNorotOp(OpKernelConstruction* context) : OpKernel(context) { + explicit ProdVirialSeAOp(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("n_a_sel", &n_a_sel)); OP_REQUIRES_OK(context, context->GetAttr("n_r_sel", &n_r_sel)); n_a_shift = n_a_sel * 4; @@ -161,7 +161,7 @@ class ProdVirialNorotOp : public OpKernel { } }; -REGISTER_KERNEL_BUILDER(Name("ProdVirialNorot").Device(DEVICE_CPU), ProdVirialNorotOp); +REGISTER_KERNEL_BUILDER(Name("ProdVirialSeA").Device(DEVICE_CPU), ProdVirialSeAOp); diff --git a/source/op/prod_virial_se_a_grad.cc b/source/op/prod_virial_se_a_grad.cc index 3263e3c0de..0d19a1c19a 100644 --- a/source/op/prod_virial_se_a_grad.cc +++ b/source/op/prod_virial_se_a_grad.cc @@ -13,7 +13,7 @@ typedef float VALUETYPE; #endif #ifdef HIGH_PREC -REGISTER_OP("ProdVirialNorotGrad") +REGISTER_OP("ProdVirialSeAGrad") .Input("grad: double") .Input("net_deriv: double") .Input("in_deriv: double") @@ -24,7 +24,7 @@ REGISTER_OP("ProdVirialNorotGrad") .Attr("n_r_sel: int") .Output("grad_net: double"); #else -REGISTER_OP("ProdVirialNorotGrad") +REGISTER_OP("ProdVirialSeAGrad") .Input("grad: float") .Input("net_deriv: float") .Input("in_deriv: float") @@ -36,10 +36,10 @@ REGISTER_OP("ProdVirialNorotGrad") .Output("grad_net: float"); #endif -class ProdVirialNorotGradOp : public OpKernel +class ProdVirialSeAGradOp : public OpKernel { public: - explicit ProdVirialNorotGradOp(OpKernelConstruction* context) : OpKernel(context) { + explicit ProdVirialSeAGradOp(OpKernelConstruction* context) : OpKernel(context) { OP_REQUIRES_OK(context, context->GetAttr("n_a_sel", &n_a_sel)); OP_REQUIRES_OK(context, context->GetAttr("n_r_sel", &n_r_sel)); n_a_shift = n_a_sel * 4; @@ -162,4 +162,4 @@ class ProdVirialNorotGradOp : public OpKernel } }; -REGISTER_KERNEL_BUILDER(Name("ProdVirialNorotGrad").Device(DEVICE_CPU), ProdVirialNorotGradOp); +REGISTER_KERNEL_BUILDER(Name("ProdVirialSeAGrad").Device(DEVICE_CPU), ProdVirialSeAGradOp); From 0e85c1a69488884bd9482c7eb70a5c7de0c846b1 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 12 Jul 2019 08:05:09 +0800 Subject: [PATCH 27/33] build_interaction -> build --- source/tests/test_model_loc_frame.py | 22 +++++++++---------- source/tests/test_model_se_a.py | 22 +++++++++---------- source/tests/test_model_se_a_fparam.py | 22 +++++++++---------- source/tests/test_model_se_a_srtab.py | 22 +++++++++---------- source/tests/test_model_se_r.py | 22 +++++++++---------- source/train/{EnerFitting.py => Fitting.py} | 0 source/train/Model.py | 24 ++++++++++----------- 7 files changed, 67 insertions(+), 67 deletions(-) rename source/train/{EnerFitting.py => Fitting.py} (100%) diff --git a/source/tests/test_model_loc_frame.py b/source/tests/test_model_loc_frame.py index ee3d772b71..c99ef57e5e 100644 --- a/source/tests/test_model_loc_frame.py +++ b/source/tests/test_model_loc_frame.py @@ -82,17 +82,17 @@ def test_model(self): t_fparam = None energy, force, virial, atom_ener, atom_virial \ - = model.build_interaction (t_coord, - t_type, - t_natoms, - t_box, - t_mesh, - t_fparam, - davg = davg, - dstd = dstd, - bias_atom_e = bias_atom_e, - suffix = "loc_frame", - reuse = False) + = model.build (t_coord, + t_type, + t_natoms, + t_box, + t_mesh, + t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "loc_frame", + reuse = False) feed_dict_test = {t_prop_c: test_prop_c, t_energy: test_energy [:numb_test], diff --git a/source/tests/test_model_se_a.py b/source/tests/test_model_se_a.py index 11d4d1a566..f502eee2ef 100644 --- a/source/tests/test_model_se_a.py +++ b/source/tests/test_model_se_a.py @@ -82,17 +82,17 @@ def test_model(self): t_fparam = None energy, force, virial, atom_ener, atom_virial \ - = model.build_interaction (t_coord, - t_type, - t_natoms, - t_box, - t_mesh, - t_fparam, - davg = davg, - dstd = dstd, - bias_atom_e = bias_atom_e, - suffix = "se_a", - reuse = False) + = model.build (t_coord, + t_type, + t_natoms, + t_box, + t_mesh, + t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "se_a", + reuse = False) feed_dict_test = {t_prop_c: test_prop_c, t_energy: test_energy [:numb_test], diff --git a/source/tests/test_model_se_a_fparam.py b/source/tests/test_model_se_a_fparam.py index 1ab94e3482..465d51a3f5 100644 --- a/source/tests/test_model_se_a_fparam.py +++ b/source/tests/test_model_se_a_fparam.py @@ -83,17 +83,17 @@ def test_model(self): is_training = tf.placeholder(tf.bool) energy, force, virial, atom_ener, atom_virial \ - = model.build_interaction (t_coord, - t_type, - t_natoms, - t_box, - t_mesh, - t_fparam, - davg = davg, - dstd = dstd, - bias_atom_e = bias_atom_e, - suffix = "se_a_fparam", - reuse = False) + = model.build (t_coord, + t_type, + t_natoms, + t_box, + t_mesh, + t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "se_a_fparam", + reuse = False) feed_dict_test = {t_prop_c: test_prop_c, t_energy: test_energy [:numb_test], diff --git a/source/tests/test_model_se_a_srtab.py b/source/tests/test_model_se_a_srtab.py index d14a81e693..144f482313 100644 --- a/source/tests/test_model_se_a_srtab.py +++ b/source/tests/test_model_se_a_srtab.py @@ -92,17 +92,17 @@ def test_model(self): t_fparam = None energy, force, virial, atom_ener, atom_virial \ - = model.build_interaction (t_coord, - t_type, - t_natoms, - t_box, - t_mesh, - t_fparam, - davg = davg, - dstd = dstd, - bias_atom_e = bias_atom_e, - suffix = "se_a_srtab", - reuse = False) + = model.build (t_coord, + t_type, + t_natoms, + t_box, + t_mesh, + t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "se_a_srtab", + reuse = False) feed_dict_test = {t_prop_c: test_prop_c, t_energy: test_energy [:numb_test], diff --git a/source/tests/test_model_se_r.py b/source/tests/test_model_se_r.py index c7f3e51c1f..e0bf8ebbf3 100644 --- a/source/tests/test_model_se_r.py +++ b/source/tests/test_model_se_r.py @@ -82,17 +82,17 @@ def test_model(self): t_fparam = None energy, force, virial, atom_ener, atom_virial \ - = model.build_interaction (t_coord, - t_type, - t_natoms, - t_box, - t_mesh, - t_fparam, - davg = davg, - dstd = dstd, - bias_atom_e = bias_atom_e, - suffix = "se_r", - reuse = False) + = model.build (t_coord, + t_type, + t_natoms, + t_box, + t_mesh, + t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "se_r", + reuse = False) feed_dict_test = {t_prop_c: test_prop_c, t_energy: test_energy [:numb_test], diff --git a/source/train/EnerFitting.py b/source/train/Fitting.py similarity index 100% rename from source/train/EnerFitting.py rename to source/train/Fitting.py diff --git a/source/train/Model.py b/source/train/Model.py index b286fae06a..c49483714e 100644 --- a/source/train/Model.py +++ b/source/train/Model.py @@ -85,18 +85,18 @@ def compute_dstats (self, reuse = None) : return self.descrpt.compute_dstats(data_coord, data_box, data_atype, natoms_vec, mesh, reuse) - def build_interaction (self, - coord_, - atype_, - natoms, - box, - mesh, - fparam, - davg = None, - dstd = None, - bias_atom_e = None, - suffix = '', - reuse = None): + def build (self, + coord_, + atype_, + natoms, + box, + mesh, + fparam, + davg = None, + dstd = None, + bias_atom_e = None, + suffix = '', + reuse = None): with tf.variable_scope('model_attr' + suffix, reuse = reuse) : t_tmap = tf.constant(' '.join(self.type_map), From 49e38c0aa6122ebf7de3abfcca381d84da7488b7 Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 12 Jul 2019 08:06:29 +0800 Subject: [PATCH 28/33] seperate loss and learning rate from trainer --- examples/water/train/water.json | 66 ++++---- examples/water/train/water_se_a.json | 71 +++++---- examples/water/train/water_se_r.json | 66 ++++---- source/train/CMakeLists.txt | 2 +- source/train/LearningRate.py | 25 +++ source/train/Loss.py | 76 +++++++++ source/train/Trainer.py | 225 ++++++++++----------------- source/train/train.py | 18 +-- 8 files changed, 311 insertions(+), 238 deletions(-) create mode 100644 source/train/LearningRate.py create mode 100644 source/train/Loss.py diff --git a/examples/water/train/water.json b/examples/water/train/water.json index 066a1c083f..cc764270fc 100644 --- a/examples/water/train/water.json +++ b/examples/water/train/water.json @@ -21,39 +21,51 @@ "neuron": [240, 120, 60, 30, 10], "resnet_dt": true, "seed": 1, - "_comment": " that's all" + "_comment": " that's all" }, - "_comment": " that's all" + "_comment": " that's all" + }, + + "learning_rate" :{ + "type": "exp", + "start_lr": 0.001, + "decay_steps": 5000, + "decay_rate": 0.95, + "_comment": "that's all" }, - "_comment": " traing controls", - "systems": ["../data/water/"], - "set_prefix": "set", - "stop_batch": 1000000, - "batch_size": 4, - "start_lr": 0.001, - "decay_steps": 5000, - "decay_rate": 0.95, + "loss" : { + "type": "std", + "start_pref_e": 0.02, + "limit_pref_e": 8, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + "_comment": "that's all" + }, - "start_pref_e": 0.02, - "limit_pref_e": 8, - "start_pref_f": 1000, - "limit_pref_f": 1, - "start_pref_v": 0, - "limit_pref_v": 0, + "_comment": " traing controls", + "training": { + "systems": ["../data/water/"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 4, - "seed": 1, + "seed": 1, - "_comment": " display and restart", - "_comment": " frequencies counted in batch", - "disp_file": "lcurve.out", - "disp_freq": 100, - "numb_test": 10, - "save_freq": 1000, - "save_ckpt": "model.ckpt", - "load_ckpt": "model.ckpt", - "disp_training": true, - "time_training": true, + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 10, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training":true, + "time_training":true, + "_comment": "that's all" + }, "_comment": "that's all" } diff --git a/examples/water/train/water_se_a.json b/examples/water/train/water_se_a.json index b460df3452..62a52b5adb 100644 --- a/examples/water/train/water_se_a.json +++ b/examples/water/train/water_se_a.json @@ -11,47 +11,58 @@ "resnet_dt": false, "axis_neuron": 16, "seed": 1, - "_comment": " that's all" + "_comment": " that's all" }, "fitting_net" : { "neuron": [240, 240, 240], "resnet_dt": true, "seed": 1, - "_comment": " that's all" + "_comment": " that's all" }, - "_comment": " that's all" + "_comment": " that's all" }, - "_comment": " traing controls", - "systems": ["../data/water/"], - "set_prefix": "set", - "stop_batch": 1000000, - "batch_size": 1, - "start_lr": 0.005, - "decay_steps": 5000, - "decay_rate": 0.95, + "learning_rate" :{ + "type": "exp", + "start_lr": 0.005, + "decay_steps": 5000, + "decay_rate": 0.95, + "_comment": "that's all" + }, + + "loss" :{ + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + "_comment": " that's all" + }, - "start_pref_e": 0.02, - "limit_pref_e": 1, - "start_pref_f": 1000, - "limit_pref_f": 1, - "start_pref_v": 0, - "limit_pref_v": 0, + "_comment": " traing controls", + "training" : { + "systems": ["../data/water/"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 1, - "seed": 1, + "seed": 1, - "_comment": " display and restart", - "_comment": " frequencies counted in batch", - "disp_file": "lcurve.out", - "disp_freq": 100, - "numb_test": 10, - "save_freq": 1000, - "save_ckpt": "model.ckpt", - "load_ckpt": "model.ckpt", - "disp_training": true, - "time_training": true, - "profiling": false, - "profiling_file": "timeline.json", + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 10, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training":true, + "time_training":true, + "profiling": false, + "profiling_file":"timeline.json", + "_comment": "that's all" + }, "_comment": "that's all" } diff --git a/examples/water/train/water_se_r.json b/examples/water/train/water_se_r.json index 32b88cd00f..ff1f37e678 100644 --- a/examples/water/train/water_se_r.json +++ b/examples/water/train/water_se_r.json @@ -19,39 +19,49 @@ "seed": 1, "_comment": "that's all" }, - "_comment": " that's all" + "_comment": " that's all" }, - "_comment": " traing controls", - "systems": ["../data/water/"], - "set_prefix": "set", - "stop_batch": 1000000, - "batch_size": 1, - "start_lr": 0.005, - "decay_steps": 5000, - "decay_rate": 0.95, + "learning_rate" : { + "start_lr": 0.005, + "decay_steps": 5000, + "decay_rate": 0.95, + "_comment": " that's all" + }, + + "loss" : { + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + "_comment": " that's all" + }, - "start_pref_e": 0.02, - "limit_pref_e": 1, - "start_pref_f": 1000, - "limit_pref_f": 1, - "start_pref_v": 0, - "limit_pref_v": 0, + "_comment": " traing controls", + "training" : { + "systems": ["../data/water/"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 1, - "seed": 1, + "seed": 1, - "_comment": " display and restart", - "_comment": " frequencies counted in batch", - "disp_file": "lcurve.out", - "disp_freq": 100, - "numb_test": 10, - "save_freq": 1000, - "save_ckpt": "model.ckpt", - "load_ckpt": "model.ckpt", - "disp_training": true, - "time_training": true, - "profiling": false, - "profiling_file": "timeline.json", + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 10, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training":true, + "time_training":true, + "profiling": false, + "profiling_file": "timeline.json", + "_comment": "that's all" + }, "_comment": "that's all" } diff --git a/source/train/CMakeLists.txt b/source/train/CMakeLists.txt index 1455ae04cc..3f31441338 100644 --- a/source/train/CMakeLists.txt +++ b/source/train/CMakeLists.txt @@ -2,7 +2,7 @@ configure_file("RunOptions.py.in" "${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py" @ONLY) -file(GLOB LIB_PY common.py DeepPot.py Data.py DataSystem.py Model*.py Descrpt*.py EnerFitting.py Trainer.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) +file(GLOB LIB_PY common.py DeepPot.py Data.py DataSystem.py Model*.py Descrpt*.py Fitting.py Loss.py LearningRate.py Trainer.py TabInter.py ${CMAKE_CURRENT_BINARY_DIR}/RunOptions.py) file(GLOB CLS_PY Local.py Slurm.py) diff --git a/source/train/LearningRate.py b/source/train/LearningRate.py new file mode 100644 index 0000000000..f7eeda8d3c --- /dev/null +++ b/source/train/LearningRate.py @@ -0,0 +1,25 @@ +import os,sys,warnings +import numpy as np +import tensorflow as tf +from deepmd.common import j_must_have, j_must_have_d, j_have + +class LearningRateExp (object) : + def __init__ (self, + jdata) : + self.decay_steps_ = j_must_have(jdata, 'decay_steps') + self.decay_rate_ = j_must_have(jdata, 'decay_rate') + self.start_lr_ = j_must_have(jdata, 'start_lr') + + def build(self, global_step) : + return tf.train.exponential_decay(self.start_lr_, + global_step, + self.decay_steps_, + self.decay_rate_, + staircase=True) + def start_lr(self) : + return self.start_lr_ + + def value (self, + batch) : + return self.start_lr_ * np.power (self.decay_rate_, (batch // self.decay_steps_)) + diff --git a/source/train/Loss.py b/source/train/Loss.py new file mode 100644 index 0000000000..78fa257654 --- /dev/null +++ b/source/train/Loss.py @@ -0,0 +1,76 @@ +import os,sys,warnings +import numpy as np +import tensorflow as tf +from deepmd.common import j_must_have, j_must_have_d, j_have + +from deepmd.RunOptions import global_tf_float_precision +from deepmd.RunOptions import global_np_float_precision +from deepmd.RunOptions import global_ener_float_precision +from deepmd.RunOptions import global_cvt_2_tf_float +from deepmd.RunOptions import global_cvt_2_ener_float + +class LossStd () : + def __init__ (self, jdata, starter_learning_rate) : + self.starter_learning_rate = starter_learning_rate + self.start_pref_e = j_must_have (jdata, 'start_pref_e') + self.limit_pref_e = j_must_have (jdata, 'limit_pref_e') + self.start_pref_f = j_must_have (jdata, 'start_pref_f') + self.limit_pref_f = j_must_have (jdata, 'limit_pref_f') + self.start_pref_v = j_must_have (jdata, 'start_pref_v') + self.limit_pref_v = j_must_have (jdata, 'limit_pref_v') + self.start_pref_ae = 0 + if j_have(jdata, 'start_pref_ae') : + self.start_pref_ae = jdata['start_pref_ae'] + self.limit_pref_ae = 0 + if j_have(jdata, 'limit_pref_ae') : + self.limit_pref_ae = jdata['limit_pref_ae'] + self.has_e = (self.start_pref_e != 0 or self.limit_pref_e != 0) + self.has_f = (self.start_pref_f != 0 or self.limit_pref_f != 0) + self.has_v = (self.start_pref_v != 0 or self.limit_pref_v != 0) + self.has_ae = (self.start_pref_ae != 0 or self.limit_pref_ae != 0) + + def build (self, + learning_rate, + natoms, + prop_c, + energy, + energy_hat, + force, + force_hat, + virial, + virial_hat, + atom_ener, + atom_ener_hat, + suffix): + l2_ener_loss = tf.reduce_mean( tf.square(energy - energy_hat), name='l2_'+suffix) + + force_reshape = tf.reshape (force, [-1]) + force_hat_reshape = tf.reshape (force_hat, [-1]) + l2_force_loss = tf.reduce_mean (tf.square(force_hat_reshape - force_reshape), name = "l2_force_" + suffix) + + virial_reshape = tf.reshape (virial, [-1]) + virial_hat_reshape = tf.reshape (virial_hat, [-1]) + l2_virial_loss = tf.reduce_mean (tf.square(virial_hat_reshape - virial_reshape), name = "l2_virial_" + suffix) + + atom_ener_reshape = tf.reshape (atom_ener, [-1]) + atom_ener_hat_reshape = tf.reshape (atom_ener_hat, [-1]) + l2_atom_ener_loss = tf.reduce_mean (tf.square(atom_ener_hat_reshape - atom_ener_reshape), name = "l2_atom_ener_" + suffix) + + atom_norm = 1./ global_cvt_2_tf_float(natoms[0]) + atom_norm_ener = 1./ global_cvt_2_ener_float(natoms[0]) + pref_e = global_cvt_2_ener_float(prop_c[0] * (self.limit_pref_e + (self.start_pref_e - self.limit_pref_e) * learning_rate / self.starter_learning_rate) ) + pref_f = global_cvt_2_tf_float(prop_c[1] * (self.limit_pref_f + (self.start_pref_f - self.limit_pref_f) * learning_rate / self.starter_learning_rate) ) + pref_v = global_cvt_2_tf_float(prop_c[2] * (self.limit_pref_v + (self.start_pref_v - self.limit_pref_v) * learning_rate / self.starter_learning_rate) ) + pref_ae= global_cvt_2_tf_float(prop_c[3] * (self.limit_pref_ae+ (self.start_pref_ae-self.limit_pref_ae) * learning_rate / self.starter_learning_rate) ) + + l2_loss = 0 + if self.has_e : + l2_loss += atom_norm_ener * (pref_e * l2_ener_loss) + if self.has_f : + l2_loss += global_cvt_2_ener_float(pref_f * l2_force_loss) + if self.has_v : + l2_loss += global_cvt_2_ener_float(atom_norm * (pref_v * l2_virial_loss)) + if self.has_ae : + l2_loss += global_cvt_2_ener_float(pref_ae * l2_atom_ener_loss) + + return l2_loss, l2_ener_loss, l2_force_loss, l2_virial_loss, l2_atom_ener_loss diff --git a/source/train/Trainer.py b/source/train/Trainer.py index 72e4db06a7..19efc3c998 100644 --- a/source/train/Trainer.py +++ b/source/train/Trainer.py @@ -11,15 +11,13 @@ from deepmd.RunOptions import global_ener_float_precision from deepmd.RunOptions import global_cvt_2_tf_float from deepmd.RunOptions import global_cvt_2_ener_float -# from ModelSeA import ModelSeA -# from ModelSeR import ModelSeR -# from ModelHyb import ModelHyb -# from ModelLocFrame import ModelLocFrame -from EnerFitting import EnerFitting +from Fitting import EnerFitting from DescrptLocFrame import DescrptLocFrame from DescrptSeA import DescrptSeA from DescrptSeR import DescrptSeR from Model import Model +from Loss import LossStd +from LearningRate import LearningRateExp from tensorflow.python.framework import ops from tensorflow.python.client import timeline @@ -52,28 +50,6 @@ def _is_subdir(path, directory): relative = os.path.relpath(path, directory) + os.sep return not relative.startswith(os.pardir + os.sep) -class LearingRate (object) : - def __init__ (self, - jdata, - tot_numb_batches) : - self.decay_steps_ = j_must_have(jdata, 'decay_steps') - self.decay_rate_ = j_must_have(jdata, 'decay_rate') - self.start_lr_ = j_must_have(jdata, 'start_lr') - self.tot_numb_batches = tot_numb_batches - - def value (self, - batch) : - return self.start_lr_ * np.power (self.decay_rate_, (batch // self.decay_steps())) - - def decay_steps (self) : -# return self.decay_steps_ * self.tot_numb_batches - return self.decay_steps_ - - def decay_rate (self) : - return self.decay_rate_ - - def start_lr (self) : - return self.start_lr_ class NNPTrainer (object): def __init__(self, @@ -87,6 +63,7 @@ def _init_param(self, jdata): model_param = j_must_have(jdata, 'model') descrpt_param = j_must_have(model_param, 'descriptor') fitting_param = j_must_have(model_param, 'fitting_net') + # descriptor descrpt_type = j_must_have(descrpt_param, 'type') if descrpt_type == 'loc_frame': @@ -95,62 +72,74 @@ def _init_param(self, jdata): self.descrpt = DescrptSeA(descrpt_param) elif descrpt_type == 'se_r' : self.descrpt = DescrptSeR(descrpt_param) - # elif model_type == 'se_ar' : - # model_param_a = j_must_have(jdata, 'model_a') - # model_param_r = j_must_have(jdata, 'model_r') - # self.model = ModelHyb(model_param_a, model_param_r) else : raise RuntimeError('unknow model type ' + model_type) + # fitting net - self.fitting = EnerFitting(fitting_param, self.descrpt) + try: + fitting_type = fitting_param['type'] + except: + fitting_type = 'ener' + if fitting_type == 'ener': + self.fitting = EnerFitting(fitting_param, self.descrpt) + else : + raise RuntimeError('unknow fitting type ' + fitting_type) + + # init model self.model = Model(model_param, self.descrpt, self.fitting) - self.numb_test = j_must_have (jdata, 'numb_test') - self.useBN = False + # learning rate + lr_param = j_must_have(jdata, 'learning_rate') + try: + lr_type = lr_param['type'] + except: + lr_type = 'exp' + if lr_type == 'exp': + self.lr = LearningRateExp(lr_param) + else : + raise RuntimeError('unknow learning_rate type ' + lr_type) + + # loss + loss_param = j_must_have(jdata, 'loss') + try: + loss_type = loss_param['type'] + except: + loss_type = 'std' + if loss_type == 'std': + self.loss = LossStd(loss_param, self.lr.start_lr()) + else : + raise RuntimeError('unknow loss type ' + loss_type) - self.start_pref_e = j_must_have (jdata, 'start_pref_e') - self.limit_pref_e = j_must_have (jdata, 'limit_pref_e') - self.start_pref_f = j_must_have (jdata, 'start_pref_f') - self.limit_pref_f = j_must_have (jdata, 'limit_pref_f') - self.start_pref_v = j_must_have (jdata, 'start_pref_v') - self.limit_pref_v = j_must_have (jdata, 'limit_pref_v') - self.start_pref_ae = 0 - if j_have(jdata, 'start_pref_ae') : - self.start_pref_ae = jdata['start_pref_ae'] - self.limit_pref_ae = 0 - if j_have(jdata, 'limit_pref_ae') : - self.limit_pref_ae = jdata['limit_pref_ae'] - self.has_e = (self.start_pref_e != 0 or self.limit_pref_e != 0) - self.has_f = (self.start_pref_f != 0 or self.limit_pref_f != 0) - self.has_v = (self.start_pref_v != 0 or self.limit_pref_v != 0) - self.has_ae = (self.start_pref_ae != 0 or self.limit_pref_ae != 0) + # training + training_param = j_must_have(jdata, 'training') + + self.numb_test = j_must_have (training_param, 'numb_test') + self.useBN = False self.disp_file = "lcurve.out" - if j_have (jdata, "disp_file") : self.disp_file = jdata["disp_file"] - self.disp_freq = j_must_have (jdata, 'disp_freq') - self.save_freq = j_must_have (jdata, 'save_freq') - self.save_ckpt = j_must_have (jdata, 'save_ckpt') + if j_have (training_param, "disp_file") : self.disp_file = training_param["disp_file"] + self.disp_freq = j_must_have (training_param, 'disp_freq') + self.save_freq = j_must_have (training_param, 'save_freq') + self.save_ckpt = j_must_have (training_param, 'save_ckpt') - self.display_in_training = j_must_have (jdata, 'disp_training') - self.timing_in_training = j_must_have (jdata, 'time_training') + self.display_in_training = j_must_have (training_param, 'disp_training') + self.timing_in_training = j_must_have (training_param, 'time_training') self.profiling = False - if j_have (jdata, 'profiling') : - self.profiling = jdata['profiling'] + if j_have (training_param, 'profiling') : + self.profiling = training_param['profiling'] if self.profiling : - self.profiling_file = j_must_have (jdata, 'profiling_file') + self.profiling_file = j_must_have (training_param, 'profiling_file') self.sys_weights = None - if j_have(jdata, 'sys_weights') : - self.sys_weights = jdata['sys_weights'] + if j_have(training_param, 'sys_weights') : + self.sys_weights = training_param['sys_weights'] def _message (self, msg) : self.run_opt.message(msg) def build (self, - data, - lr) : - self.lr = lr + data) : self.ntypes = self.model.get_ntypes() assert (self.ntypes == data.get_ntypes()), "ntypes should match that found in data" @@ -174,20 +163,15 @@ def build (self, with tf.device(tf.train.replica_device_setter(worker_device = worker_device, cluster = self.run_opt.cluster_spec)): - self._build_lr(lr) + self._build_lr() self._build_network(davg, dstd, bias_e) self._build_training() - def _build_lr(self, lr): + def _build_lr(self): self._extra_train_ops = [] self.global_step = tf.train.get_or_create_global_step() - self.starter_learning_rate = lr.start_lr() - self.learning_rate = tf.train.exponential_decay(lr.start_lr(), - self.global_step, - lr.decay_steps(), - lr.decay_rate(), - staircase=True) + self.learning_rate = self.lr.build(self.global_step) self._message("built lr") def _build_network(self, davg, dstd, bias_atom_e): @@ -209,26 +193,27 @@ def _build_network(self, davg, dstd, bias_atom_e): self.t_fparam = None self.energy, self.force, self.virial, self.atom_ener, self.atom_virial\ - = self.model.build_interaction (self.t_coord, - self.t_type, - self.t_natoms, - self.t_box, - self.t_mesh, - self.t_fparam, - davg = davg, - dstd = dstd, - bias_atom_e = bias_atom_e, - suffix = "", - reuse = False) + = self.model.build (self.t_coord, + self.t_type, + self.t_natoms, + self.t_box, + self.t_mesh, + self.t_fparam, + davg = davg, + dstd = dstd, + bias_atom_e = bias_atom_e, + suffix = "", + reuse = False) self.l2_l, self.l2_el, self.l2_fl, self.l2_vl, self.l2_ael \ - = self.loss (self.t_natoms, \ - self.t_prop_c, \ - self.t_energy, self.energy, \ - self.t_force, self.force, \ - self.t_virial, self.virial, \ - self.t_atom_ener, self.atom_ener, \ - suffix = "test") + = self.loss.build (self.learning_rate, + self.t_natoms, \ + self.t_prop_c, \ + self.t_energy, self.energy, \ + self.t_force, self.force, \ + self.t_virial, self.virial, \ + self.t_atom_ener, self.atom_ener, \ + suffix = "test") self._message("built network") @@ -412,13 +397,13 @@ def print_head (self) : print_str = "# %5s" % 'batch' prop_fmt = ' %9s %9s' print_str += prop_fmt % ('l2_tst', 'l2_trn') - if self.has_e : + if self.loss.has_e : print_str += prop_fmt % ('l2_e_tst', 'l2_e_trn') - if self.has_ae : + if self.loss.has_ae : print_str += prop_fmt % ('l2_ae_tst', 'l2_ae_trn') - if self.has_f : + if self.loss.has_f : print_str += prop_fmt % ('l2_f_tst', 'l2_f_trn') - if self.has_v : + if self.loss.has_v : print_str += prop_fmt % ('l2_v_tst', 'l2_v_trn') print_str += ' %8s\n' % 'lr' fp.write(print_str) @@ -467,60 +452,16 @@ def test_on_the_fly (self, print_str = "%7d" % cur_batch prop_fmt = " %9.2e %9.2e" print_str += prop_fmt % (np.sqrt(error_test), np.sqrt(error_train)) - if self.has_e : + if self.loss.has_e : print_str += prop_fmt % (np.sqrt(error_e_test) / natoms_vec[0], np.sqrt(error_e_train) / natoms_vec[0]) - if self.has_ae : + if self.loss.has_ae : print_str += prop_fmt % (np.sqrt(error_ae_test), np.sqrt(error_ae_train)) - if self.has_f : + if self.loss.has_f : print_str += prop_fmt % (np.sqrt(error_f_test), np.sqrt(error_f_train)) - if self.has_v : + if self.loss.has_v : print_str += prop_fmt % (np.sqrt(error_v_test) / natoms_vec[0], np.sqrt(error_v_train) / natoms_vec[0]) print_str += " %8.1e\n" % current_lr fp.write(print_str) fp.flush () - def loss (self, - natoms, - prop_c, - energy, - energy_hat, - force, - force_hat, - virial, - virial_hat, - atom_ener, - atom_ener_hat, - suffix): - l2_ener_loss = tf.reduce_mean( tf.square(energy - energy_hat), name='l2_'+suffix) - - force_reshape = tf.reshape (force, [-1]) - force_hat_reshape = tf.reshape (force_hat, [-1]) - l2_force_loss = tf.reduce_mean (tf.square(force_hat_reshape - force_reshape), name = "l2_force_" + suffix) - - virial_reshape = tf.reshape (virial, [-1]) - virial_hat_reshape = tf.reshape (virial_hat, [-1]) - l2_virial_loss = tf.reduce_mean (tf.square(virial_hat_reshape - virial_reshape), name = "l2_virial_" + suffix) - - atom_ener_reshape = tf.reshape (atom_ener, [-1]) - atom_ener_hat_reshape = tf.reshape (atom_ener_hat, [-1]) - l2_atom_ener_loss = tf.reduce_mean (tf.square(atom_ener_hat_reshape - atom_ener_reshape), name = "l2_atom_ener_" + suffix) - - atom_norm = 1./ global_cvt_2_tf_float(natoms[0]) - atom_norm_ener = 1./ global_cvt_2_ener_float(natoms[0]) - pref_e = global_cvt_2_ener_float(prop_c[0] * (self.limit_pref_e + (self.start_pref_e - self.limit_pref_e) * self.learning_rate / self.starter_learning_rate) ) - pref_f = global_cvt_2_tf_float(prop_c[1] * (self.limit_pref_f + (self.start_pref_f - self.limit_pref_f) * self.learning_rate / self.starter_learning_rate) ) - pref_v = global_cvt_2_tf_float(prop_c[2] * (self.limit_pref_v + (self.start_pref_v - self.limit_pref_v) * self.learning_rate / self.starter_learning_rate) ) - pref_ae= global_cvt_2_tf_float(prop_c[3] * (self.limit_pref_ae+ (self.start_pref_ae-self.limit_pref_ae) * self.learning_rate / self.starter_learning_rate) ) - - l2_loss = 0 - if self.has_e : - l2_loss += atom_norm_ener * (pref_e * l2_ener_loss) - if self.has_f : - l2_loss += global_cvt_2_ener_float(pref_f * l2_force_loss) - if self.has_v : - l2_loss += global_cvt_2_ener_float(atom_norm * (pref_v * l2_virial_loss)) - if self.has_ae : - l2_loss += global_cvt_2_ener_float(pref_ae * l2_atom_ener_loss) - - return l2_loss, l2_ener_loss, l2_force_loss, l2_virial_loss, l2_atom_ener_loss diff --git a/source/train/train.py b/source/train/train.py index 3815081cb9..a7ccb987fe 100755 --- a/source/train/train.py +++ b/source/train/train.py @@ -14,7 +14,6 @@ from deepmd.RunOptions import RunOptions from deepmd.DataSystem import DataSystem from deepmd.Trainer import NNPTrainer -from deepmd.Trainer import LearingRate def create_done_queue(cluster_spec, task_index): with tf.device("/job:ps/task:%d" % (task_index)): @@ -84,22 +83,21 @@ def _do_work(jdata, run_opt): model = NNPTrainer (jdata, run_opt = run_opt) rcut = model.model.get_rcut() # init params and run options - systems = j_must_have(jdata, 'systems') - set_pfx = j_must_have(jdata, 'set_prefix') + assert('training' in jdata) + systems = j_must_have(jdata['training'], 'systems') + set_pfx = j_must_have(jdata['training'], 'set_prefix') numb_sys = len(systems) seed = None - if 'seed' in jdata.keys() : seed = jdata['seed'] + if 'seed' in jdata['training'].keys() : seed = jdata['training']['seed'] if seed is not None: seed = seed % (2**32) np.random.seed (seed) - batch_size = j_must_have(jdata, 'batch_size') - test_size = j_must_have(jdata, 'numb_test') - stop_batch = j_must_have(jdata, 'stop_batch') + batch_size = j_must_have(jdata['training'], 'batch_size') + test_size = j_must_have(jdata['training'], 'numb_test') + stop_batch = j_must_have(jdata['training'], 'stop_batch') data = DataSystem(systems, set_pfx, batch_size, test_size, rcut, run_opt) - tot_numb_batches = sum(data.get_nbatches()) - lr = LearingRate (jdata, tot_numb_batches) # build the model with stats from the first system - model.build (data, lr) + model.build (data) # train the model with the provided systems in a cyclic way start_time = time.time() cur_batch = 0 From 50927632118e735886df479b053c567d7de8ae0c Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 12 Jul 2019 08:07:00 +0800 Subject: [PATCH 29/33] change version format --- source/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/setup.py b/source/setup.py index c46299f320..7f57a218bc 100644 --- a/source/setup.py +++ b/source/setup.py @@ -19,7 +19,7 @@ setup( name="deepmd-kit", setup_requires=['setuptools-git-version'], - version_format='{tag}.dev{commitcount}+{gitsha}', + version_format='{tag}.dev{commitcount}_{gitsha}', author="Han Wang", author_email="wang_han@iapcm.ac.cn", description="A deep learning package for many-body potential energy representation and molecular dynamics", From 59b950a15a1798d904ba5321d13fcf8937ecbe7b Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 12 Jul 2019 08:07:24 +0800 Subject: [PATCH 30/33] rm outdated test --- source/train/TestNorot.py | 466 -------------------------------------- 1 file changed, 466 deletions(-) delete mode 100644 source/train/TestNorot.py diff --git a/source/train/TestNorot.py b/source/train/TestNorot.py deleted file mode 100644 index 449adc8258..0000000000 --- a/source/train/TestNorot.py +++ /dev/null @@ -1,466 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -import time -import numpy as np -import glob -import tensorflow as tf - -from tensorflow.python.framework import ops - -# load force module -module_path = os.path.dirname(os.path.realpath(__file__)) + "/" -assert (os.path.isfile (module_path + "libop_abi.so" )), "op module does not exist" -op_module = tf.load_op_library(module_path + "libop_abi.so") - -# load grad of force module -sys.path.append (module_path ) -import _prod_force_se_a_grad -import _prod_virial_se_a_grad - -class DataSets (object): - def __init__ (self, - set_prefix = "set", - hh = 1e-6, - seed = None) : - self.dirs = glob.glob (set_prefix + ".*") - self.dirs.sort() - self.test_dir = self.dirs[-1] - self.set_count = 0 - self.hh = hh - self.load_test_set (self.test_dir) - - def get_numb_set (self) : - return len (self.train_dirs) - - def stats_energy (self) : - eners = [] - for ii in self.dirs: - ei = np.load (ii + "/energy.npy") - eners.append (np.average(ei)) - return np.average (eners) - - def load_test_set (self, - set_name) : - start_time = time.time() - coord_test = np.load (set_name + "/coord.npy") - box_test = np.load (set_name + "/box.npy") - # dirty workaround, type in type.raw should be sorted - type_test = np.loadtxt (set_name + "/../type.raw") - - self.coord_test0 = np.array([coord_test[0]]) - self.box_test0 = np.array([box_test[0]]) - self.type_test0 = np.array([type_test]) - - self.coord_test = [coord_test[0]] - self.box_test = [box_test[0]] - self.type_test = np.array([type_test]) - - coord0 = np.copy (self.coord_test[0]) - - self.natoms = self.type_test[0].shape[0] - for ii in range(self.natoms * 3) : - p_coord = np.copy (coord0) - n_coord = np.copy (coord0) - p_coord[ii] += self.hh - n_coord[ii] -= self.hh - self.coord_test.append (p_coord) - self.coord_test.append (n_coord) - self.box_test.append (box_test[0]) - self.box_test.append (box_test[0]) - - self.coord_test = np.array(self.coord_test) - self.box_test = np.array(self.box_test) - self.type_test = np.tile (self.type_test, (2 * self.natoms * 3 + 1, 1)) - - end_time = time.time() - - def get_test (self) : - return self.coord_test, self.box_test, self.type_test - - def get_test0 (self) : - return self.coord_test0, self.box_test0, self.type_test0 - - def get_test_box (self, - hh) : - coord0_, box0_, type0_ = self.get_test0() - coord0 = coord0_[0] - box0 = box0_[0] - type0 = type0_[0] - nc = np.array( [coord0, coord0*(1+hh), coord0*(1-hh)] ) - nb = np.array( [box0, box0*(1+hh), box0*(1-hh)] ) - nt = np.array( [type0, type0, type0] ) - for dd in range(3) : - tmpc = np.copy (coord0) - tmpb = np.copy (box0) - tmpc = np.reshape(tmpc, [-1, 3]) - tmpc [:,dd] *= (1+hh) - tmpc = np.reshape(tmpc, [-1]) - tmpb = np.reshape(tmpb, [-1, 3]) - tmpb [dd,:] *= (1+hh) - tmpb = np.reshape(tmpb, [-1]) - nc = np.append (nc, [tmpc], axis = 0) - nb = np.append (nb, [tmpb], axis = 0) - nt = np.append (nt, [type0], axis = 0) - tmpc = np.copy (coord0) - tmpb = np.copy (box0) - tmpc = np.reshape(tmpc, [-1, 3]) - tmpc [:,dd] *= (1-hh) - tmpc = np.reshape(tmpc, [-1]) - tmpb = np.reshape(tmpb, [-1, 3]) - tmpb [dd,:] *= (1-hh) - tmpb = np.reshape(tmpb, [-1]) - nc = np.append (nc, [tmpc], axis = 0) - nb = np.append (nb, [tmpb], axis = 0) - nt = np.append (nt, [type0], axis = 0) - return nc, nb, nt - - def get_natoms (self) : - ntype1 = np.sum (self.type_test0) - tmp = np.array([self.natoms, self.natoms, self.natoms - ntype1, ntype1]) - return tmp.astype(np.int32) - - def get_h (self) : - return self.hh - -class Model (object) : - def __init__ (self, - sess, - data, - comp = 0) : - self.sess = sess - self.natoms = data.get_natoms() - self.comp = comp - self.sel_a = [12, 24] - self.sel_r = [0,0] - self.rcut_a = -1 - self.rcut_r_smth = 2.45 - self.rcut_r = 3.45 - self.axis_rule = [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0] - self.nnei_a = np.cumsum(self.sel_a)[-1] - self.nnei_r = np.cumsum(self.sel_r)[-1] - self.nnei = self.nnei_a + self.nnei_r - self.ndescrpt_a = self.nnei_a * 4 - self.ndescrpt_r = self.nnei_r * 1 - self.ndescrpt = self.ndescrpt_a + self.ndescrpt_r - davg = np.zeros (self.ndescrpt) - dstd = np.ones (self.ndescrpt) - self.t_avg = tf.constant(davg.astype(np.float64)) - self.t_std = tf.constant(dstd.astype(np.float64)) - self.default_mesh = np.zeros (6, dtype = np.int32) - self.default_mesh[3] = 2 - self.default_mesh[4] = 2 - self.default_mesh[5] = 2 - - def net (self, - inputs, - name, - reuse = False) : - with tf.variable_scope(name, reuse=reuse): - net_w = tf.get_variable ('net_w', - [self.ndescrpt], - tf.float64, - tf.constant_initializer (self.net_w_i)) - dot_v = tf.matmul (tf.reshape (inputs, [-1, self.ndescrpt]), - tf.reshape (net_w, [self.ndescrpt, 1])) - return tf.reshape (dot_v, [-1]) - - def comp_descrpt (self, - dcoord, - dbox, - dtype, - tnatoms, - name) : - descrpt, descrpt_deriv, rij, nlist, \ - = op_module.descrpt_se_a (dcoord, - dtype, - tnatoms, - dbox, - tf.constant(self.default_mesh), - self.t_avg, - self.t_std, - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - rcut_r_smth = self.rcut_r_smth, - sel_a = self.sel_a, - sel_r = self.sel_r) - return descrpt, descrpt_deriv - - def comp_ef (self, - dcoord, - dbox, - dtype, - tnatoms, - name, - reuse = None) : - descrpt, descrpt_deriv, rij, nlist, \ - = op_module.descrpt_se_a (dcoord, - dtype, - tnatoms, - dbox, - tf.constant(self.default_mesh), - self.t_avg, - self.t_std, - rcut_a = self.rcut_a, - rcut_r = self.rcut_r, - rcut_r_smth = self.rcut_r_smth, - sel_a = self.sel_a, - sel_r = self.sel_r) - inputs_reshape = tf.reshape (descrpt, [-1, self.ndescrpt]) - atom_ener = self.net (inputs_reshape, name, reuse = reuse) - atom_ener_reshape = tf.reshape(atom_ener, [-1, self.natoms[0]]) - energy = tf.reduce_sum (atom_ener_reshape, axis = 1) - net_deriv_ = tf.gradients (atom_ener, inputs_reshape) - net_deriv = net_deriv_[0] - net_deriv_reshape = tf.reshape (net_deriv, [-1, self.natoms[0] * self.ndescrpt]) - - force = op_module.prod_force_se_a (net_deriv_reshape, - descrpt_deriv, - nlist, - tnatoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - virial, atom_vir = op_module.prod_virial_se_a (net_deriv_reshape, - descrpt_deriv, - rij, - nlist, - tnatoms, - n_a_sel = self.nnei_a, - n_r_sel = self.nnei_r) - return energy, force, virial - - def comp_fl (self, - dcoord, - dbox, - dtype, - tnatoms, - name, - reuse = None) : - energy, force, virial = self.comp_ef (dcoord, dbox, dtype, tnatoms, name, reuse) - with tf.variable_scope(name, reuse=True): - net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) - f_mag = tf.reduce_sum (tf.nn.tanh(force)) - f_mag_dw = tf.gradients (f_mag, net_w) - assert (len(f_mag_dw) == 1), "length of dw is wrong" - return f_mag, f_mag_dw[0] - - def comp_vl (self, - dcoord, - dbox, - dtype, - tnatoms, - name, - reuse = None) : - energy, force, virial = self.comp_ef (dcoord, dbox, dtype, tnatoms, name, reuse) - with tf.variable_scope(name, reuse=True): - net_w = tf.get_variable ('net_w', [self.ndescrpt], tf.float64, tf.constant_initializer (self.net_w_i)) - v_mag = tf.reduce_sum (virial) - v_mag_dw = tf.gradients (v_mag, net_w) - assert (len(v_mag_dw) == 1), "length of dw is wrong" - return v_mag, v_mag_dw[0] - - def make_place (self) : - self.coord = tf.placeholder(tf.float64, [None, self.natoms[0] * 3], name='t_coord') - self.box = tf.placeholder(tf.float64, [None, 9], name='t_box') - self.type = tf.placeholder(tf.int32, [None, self.natoms[0]], name = "t_type") - self.tnatoms = tf.placeholder(tf.int32, [None], name = "t_natoms") - - def make_feed_dict (self, - data ) : - dcoord, dbox, dtype = data.get_test () - return {self.coord: dcoord, - self.box: dbox, - self.type: dtype, - self.tnatoms: self.natoms, - } - - def make_feed_dict0 (self, - data ) : - dcoord, dbox, dtype = data.get_test0 () - return {self.coord: dcoord, - self.box: dbox, - self.type: dtype, - self.tnatoms: self.natoms, - } - - def test_descrpt (self, - data) : - self.make_place () - feed_dict_test = self.make_feed_dict (data) - feed_dict_test0 = self.make_feed_dict0 (data) - - self.net_w_i = 1 * np.ones (self.ndescrpt) - t_descrpt, t_descrpt_deriv = self.comp_descrpt (self.coord, self.box, self.type, self.tnatoms, name = "test_0") - - self.sess.run (tf.global_variables_initializer()) - [mydescrpt, mydescrpt_deriv] = self.sess.run ([t_descrpt, t_descrpt_deriv], feed_dict = feed_dict_test) - - print (mydescrpt) - print (mydescrpt.shape) - print (mydescrpt_deriv) - print (mydescrpt_deriv.shape) - - - def test_force (self, - data) : - self.make_place () - feed_dict_test = self.make_feed_dict (data) - feed_dict_test0 = self.make_feed_dict0 (data) - - self.net_w_i = 1 * np.ones (self.ndescrpt) - t_energy, t_force, t_virial = self.comp_ef (self.coord, self.box, self.type, self.tnatoms, name = "test_0") - self.sess.run (tf.global_variables_initializer()) - energy = self.sess.run (t_energy, feed_dict = feed_dict_test) - force = self.sess.run (t_force , feed_dict = feed_dict_test) - # virial = self.sess.run (t_virial , feed_dict = feed_dict_test) - - hh2 = data.get_h() * 2. - ndof = (len(energy) - 1) // 2 - absolut_e = [] - relativ_e = [] - for ii in range (ndof) : - idx0 = ii * 2 + 1 - idx1 = ii * 2 + 2 - # +hh -hh - num_force = - (energy[idx0] - energy[idx1]) / hh2 - ana_force = force[0][ii] - diff = np.abs(num_force - ana_force) - absolut_e.append (diff) - relativ_e.append (diff / np.abs(ana_force)) - print ("component %6u \t value %12.5e numerical %12.5e \t diff: %10.2e \t relat: %10.2e" % (ii, ana_force, num_force, diff, np.abs(diff/ana_force))) - - print ("max absolute %e" % np.max(absolut_e)) - print ("max relative %e" % np.max(relativ_e)) - - def comp_vol (self, - box) : - return np.linalg.det (np.reshape(box, (3,3))) - - def test_virial (self, - data ) : - hh = 1e-6 - - self.make_place () - dcoord, dbox, dtype = data.get_test_box (hh) - feed_dict_box = {self.coord: dcoord, - self.box: dbox, - self.type: dtype, - self.tnatoms: self.natoms, - } - - self.net_w_i = 1 * np.ones (self.ndescrpt) - - t_energy, t_force, t_virial = self.comp_ef (self.coord, self.box, self.type, self.tnatoms, name = "test_0") - self.sess.run (tf.global_variables_initializer()) - virial = self.sess.run (t_virial , feed_dict = feed_dict_box) - energy = self.sess.run (t_energy , feed_dict = feed_dict_box) - - print ("printing virial") - ana_vir3 = (virial[0][0] + virial[0][4] + virial[0][8])/3. / self.comp_vol(dbox[0]) - num_vir3 = -(energy[1] - energy[2]) / (self.comp_vol(dbox[1]) - self.comp_vol(dbox[2])) - print ( "all-dir: ana %14.5e num %14.5e diff %.2e" % (ana_vir3, num_vir3, np.abs(ana_vir3 - num_vir3)) ) - vir_idx = [0, 4, 8] - ana_v = [] - num_v = [] - for dd in range (3) : - ana_v.append (virial[0][vir_idx[dd]] / self.comp_vol(dbox[0])) - idx = 2 * (dd+1) + 1 - num_v.append ( -(energy[idx] - energy[idx+1]) / (self.comp_vol(dbox[idx]) - self.comp_vol(dbox[idx+1])) ) - for dd in range (3) : - print ( "dir %d: ana %14.5e num %14.5e diff %.2e" % (dd, ana_v[dd], num_v[dd], np.abs(ana_v[dd] - num_v[dd])) ) - - def test_dw (self, - data) : - self.make_place () - feed_dict_test0 = self.make_feed_dict0 (data) - - w0 = np.ones (self.ndescrpt) - self.net_w_i = np.copy(w0) - - t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_0") - self.sess.run (tf.global_variables_initializer()) - ll_0 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - dw_0 = self.sess.run (t_dw, feed_dict = feed_dict_test0) - - hh = 1e-4 - absolut_e = [] - relativ_e = [] - for ii in range (self.ndescrpt) : - self.net_w_i = np.copy (w0) - self.net_w_i[ii] += hh - t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+1)) - self.sess.run (tf.global_variables_initializer()) - ll_1 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - self.net_w_i[ii] -= 2. * hh - t_ll, t_dw = self.comp_fl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+2)) - self.sess.run (tf.global_variables_initializer()) - ll_2 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - num_v = (ll_1 - ll_2) / (2. * hh) - ana_v = dw_0[ii] - diff = np.abs (num_v - ana_v) - if (np.abs(ana_v) < 1e-10) : - diff_r = diff - else : - diff_r = diff / np.abs(ana_v) - print ("component %6u \t value %12.5e n_v %.12e \t diff: %10.2e \t relat: %10.2e" % (ii, ana_v, num_v, diff, diff_r)) - absolut_e.append (diff) - relativ_e.append (diff_r) - - print ("max absolute %e" % np.max(absolut_e)) - print ("max relative %e" % np.max(relativ_e)) - - def test_virial_dw (self, - data) : - self.make_place () - feed_dict_test0 = self.make_feed_dict0 (data) - - w0 = np.ones (self.ndescrpt) - self.net_w_i = np.copy(w0) - - t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_0") - self.sess.run (tf.global_variables_initializer()) - ll_0 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - dw_0 = self.sess.run (t_dw, feed_dict = feed_dict_test0) - - hh = 1e-4 - absolut_e = [] - relativ_e = [] - for ii in range (self.ndescrpt) : - self.net_w_i = np.copy (w0) - self.net_w_i[ii] += hh - t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+1)) - self.sess.run (tf.global_variables_initializer()) - ll_1 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - self.net_w_i[ii] -= 2. * hh - t_ll, t_dw = self.comp_vl (self.coord, self.box, self.type, self.tnatoms, name = "test_" + str(ii*2+2)) - self.sess.run (tf.global_variables_initializer()) - ll_2 = self.sess.run (t_ll, feed_dict = feed_dict_test0) - num_v = (ll_1 - ll_2) / (2. * hh) - ana_v = dw_0[ii] - diff = np.abs (num_v - ana_v) - if (np.abs(ana_v) < 1e-10) : - diff_r = diff - else : - diff_r = diff / np.abs(ana_v) - print ("component %6u \t value %12.5e n_v %12.5e \t diff: %10.2e \t relat: %10.2e" % (ii, ana_v, num_v, diff, diff_r)) - absolut_e.append (diff) - relativ_e.append (diff_r) - - print ("max absolute %e" % np.max(absolut_e)) - print ("max relative %e" % np.max(relativ_e)) - -def _main () : - data = DataSets (set_prefix = "set") - tf.reset_default_graph() - - with tf.Session() as sess: - md = Model (sess, data) - md.test_force (data) - # md.test_virial (data) - # md.test_dw (data) - # md.test_virial_dw (data) - -if __name__ == '__main__': - _main() - From a57e42340b4bcf347168cb67af48332c64daa50b Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 12 Jul 2019 08:09:18 +0800 Subject: [PATCH 31/33] add example for short-range tab --- examples/water/train/water_srtab_example.json | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 examples/water/train/water_srtab_example.json diff --git a/examples/water/train/water_srtab_example.json b/examples/water/train/water_srtab_example.json new file mode 100644 index 0000000000..1f9f832278 --- /dev/null +++ b/examples/water/train/water_srtab_example.json @@ -0,0 +1,74 @@ +{ + "with_distrib": false, + "_comment": " model parameters", + "model":{ + "type_map": ["O", "H"], + "use_srtab": "your_tab", + "smin_alpha": float_alpha, + "sw_rmin": float_rmin, + "sw_rmax": float_rmax, + "descriptor": { + "type": "loc_frame", + "sel_a": [16, 32], + "sel_r": [30, 60], + "rcut": 6.00, + "axis_rule": [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0], + "_comment": " default rule: []", + "_comment": " user defined rule: for each type provides two axes, ", + "_comment": " for each axis: (a_or_r, type, idx)", + "_comment": " if type < 0, exclude type -(type+1)", + "_comment": " for water (O:0, H:1) it can be", + "_comment": " [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0]", + "_comment": " that's all" + }, + "fitting_net": { + "neuron": [240, 120, 60, 30, 10], + "resnet_dt": true, + "seed": 1, + "_comment": " that's all" + }, + "_comment": " that's all" + }, + + "learning_rate" :{ + "type": "exp", + "start_lr": 0.001, + "decay_steps": 5000, + "decay_rate": 0.95, + "_comment": "that's all" + }, + + "loss" : { + "start_pref_e": 0.02, + "limit_pref_e": 8, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + "_comment": " that's all" + }, + + "_comment": " training controls", + "training" : { + "systems": ["../data/water/"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 4, + + "seed": 1, + + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 10, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training":true, + "time_training":true, + "_comment": "that's all" + }, + "_comment": "that's all" +} + From 3a24677d6feb1915ab6caa5f196d3e684c5ec1de Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 12 Jul 2019 08:30:46 +0800 Subject: [PATCH 32/33] add example for fparam --- examples/fparam/data/.gitignore | 2 + .../fparam/data/e3000_i2000/set.000/box.npy | Bin 0 -> 3728 bytes .../fparam/data/e3000_i2000/set.000/coord.npy | Bin 0 -> 64928 bytes .../data/e3000_i2000/set.000/energy.npy | Bin 0 -> 528 bytes .../fparam/data/e3000_i2000/set.000/force.npy | Bin 0 -> 64928 bytes .../data/e3000_i2000/set.000/fparam.npy | Bin 0 -> 528 bytes .../fparam/data/e3000_i2000/set.001/box.npy | Bin 0 -> 3728 bytes .../fparam/data/e3000_i2000/set.001/coord.npy | Bin 0 -> 64928 bytes .../data/e3000_i2000/set.001/energy.npy | Bin 0 -> 528 bytes .../fparam/data/e3000_i2000/set.001/force.npy | Bin 0 -> 64928 bytes .../data/e3000_i2000/set.001/fparam.npy | Bin 0 -> 528 bytes .../fparam/data/e3000_i2000/set.002/box.npy | Bin 0 -> 3728 bytes .../fparam/data/e3000_i2000/set.002/coord.npy | Bin 0 -> 64928 bytes .../data/e3000_i2000/set.002/energy.npy | Bin 0 -> 528 bytes .../fparam/data/e3000_i2000/set.002/force.npy | Bin 0 -> 64928 bytes .../data/e3000_i2000/set.002/fparam.npy | Bin 0 -> 528 bytes examples/fparam/data/e3000_i2000/type.raw | 54 +++++++++++++++ .../fparam/data/e8000_i2000/set.000/box.npy | Bin 0 -> 3728 bytes .../fparam/data/e8000_i2000/set.000/coord.npy | Bin 0 -> 64928 bytes .../data/e8000_i2000/set.000/energy.npy | Bin 0 -> 528 bytes .../fparam/data/e8000_i2000/set.000/force.npy | Bin 0 -> 64928 bytes .../data/e8000_i2000/set.000/fparam.npy | Bin 0 -> 528 bytes .../fparam/data/e8000_i2000/set.001/box.npy | Bin 0 -> 3728 bytes .../fparam/data/e8000_i2000/set.001/coord.npy | Bin 0 -> 64928 bytes .../data/e8000_i2000/set.001/energy.npy | Bin 0 -> 528 bytes .../fparam/data/e8000_i2000/set.001/force.npy | Bin 0 -> 64928 bytes .../data/e8000_i2000/set.001/fparam.npy | Bin 0 -> 528 bytes .../fparam/data/e8000_i2000/set.002/box.npy | Bin 0 -> 3728 bytes .../fparam/data/e8000_i2000/set.002/coord.npy | Bin 0 -> 64928 bytes .../data/e8000_i2000/set.002/energy.npy | Bin 0 -> 528 bytes .../fparam/data/e8000_i2000/set.002/force.npy | Bin 0 -> 64928 bytes .../data/e8000_i2000/set.002/fparam.npy | Bin 0 -> 528 bytes examples/fparam/data/e8000_i2000/type.raw | 54 +++++++++++++++ examples/fparam/lmp/.gitignore | 3 + examples/fparam/lmp/conf.lmp | 64 ++++++++++++++++++ examples/fparam/lmp/in.lammps | 25 +++++++ examples/fparam/train/.gitignore | 5 ++ examples/fparam/train/input.json | 62 +++++++++++++++++ 38 files changed, 269 insertions(+) create mode 100644 examples/fparam/data/.gitignore create mode 100644 examples/fparam/data/e3000_i2000/set.000/box.npy create mode 100644 examples/fparam/data/e3000_i2000/set.000/coord.npy create mode 100644 examples/fparam/data/e3000_i2000/set.000/energy.npy create mode 100644 examples/fparam/data/e3000_i2000/set.000/force.npy create mode 100644 examples/fparam/data/e3000_i2000/set.000/fparam.npy create mode 100644 examples/fparam/data/e3000_i2000/set.001/box.npy create mode 100644 examples/fparam/data/e3000_i2000/set.001/coord.npy create mode 100644 examples/fparam/data/e3000_i2000/set.001/energy.npy create mode 100644 examples/fparam/data/e3000_i2000/set.001/force.npy create mode 100644 examples/fparam/data/e3000_i2000/set.001/fparam.npy create mode 100644 examples/fparam/data/e3000_i2000/set.002/box.npy create mode 100644 examples/fparam/data/e3000_i2000/set.002/coord.npy create mode 100644 examples/fparam/data/e3000_i2000/set.002/energy.npy create mode 100644 examples/fparam/data/e3000_i2000/set.002/force.npy create mode 100644 examples/fparam/data/e3000_i2000/set.002/fparam.npy create mode 100644 examples/fparam/data/e3000_i2000/type.raw create mode 100644 examples/fparam/data/e8000_i2000/set.000/box.npy create mode 100644 examples/fparam/data/e8000_i2000/set.000/coord.npy create mode 100644 examples/fparam/data/e8000_i2000/set.000/energy.npy create mode 100644 examples/fparam/data/e8000_i2000/set.000/force.npy create mode 100644 examples/fparam/data/e8000_i2000/set.000/fparam.npy create mode 100644 examples/fparam/data/e8000_i2000/set.001/box.npy create mode 100644 examples/fparam/data/e8000_i2000/set.001/coord.npy create mode 100644 examples/fparam/data/e8000_i2000/set.001/energy.npy create mode 100644 examples/fparam/data/e8000_i2000/set.001/force.npy create mode 100644 examples/fparam/data/e8000_i2000/set.001/fparam.npy create mode 100644 examples/fparam/data/e8000_i2000/set.002/box.npy create mode 100644 examples/fparam/data/e8000_i2000/set.002/coord.npy create mode 100644 examples/fparam/data/e8000_i2000/set.002/energy.npy create mode 100644 examples/fparam/data/e8000_i2000/set.002/force.npy create mode 100644 examples/fparam/data/e8000_i2000/set.002/fparam.npy create mode 100644 examples/fparam/data/e8000_i2000/type.raw create mode 100644 examples/fparam/lmp/.gitignore create mode 100644 examples/fparam/lmp/conf.lmp create mode 100644 examples/fparam/lmp/in.lammps create mode 100644 examples/fparam/train/.gitignore create mode 100644 examples/fparam/train/input.json diff --git a/examples/fparam/data/.gitignore b/examples/fparam/data/.gitignore new file mode 100644 index 0000000000..b440c7f944 --- /dev/null +++ b/examples/fparam/data/.gitignore @@ -0,0 +1,2 @@ +*raw + diff --git a/examples/fparam/data/e3000_i2000/set.000/box.npy b/examples/fparam/data/e3000_i2000/set.000/box.npy new file mode 100644 index 0000000000000000000000000000000000000000..23b62d305eddfe4762846027b04e009b5d90b479 GIT binary patch literal 3728 zcmeH_F$%&k07c{KDKZ&_4i#J!7Z*3h#lcC6O%ah+5^)hv;px1M4MKm1KRN$Q?Mwdh zc<=3gvpWpT!#rK-<0-g>b<1kzGV7`)+(S_xn^5}1*TpsZmdA5(^X>n5n&+9FPcr+m zf1csxGg_J6VgHQvCMMnM4U8}sjA3EqP#tzTuvQrB2JJ_>*BcmNE*Qf?4&?_mFc*wr ip?>5CH82;9VWEEH2Q@GkjA5aE*ou$ln&XzdA8Cyp4cy4S6Y``*&#SR_P6^nuLBi-?Iut%yRoFG(5(&30 zaqi7Eqkl@yu)p1i@^RWUFu@=94(U_!DqHL~^oG?WUo^a049k~0(G^>Zz%&b(UR#BJ z{r?Fuw_9=MnK?}Mc;Z4r0d$O<@b_9dc2hm}c{#xRi#}C_zLMTKUWJ>T4sdLi&PTf|S?gej`kSa7!xuS?BfSXqMC`*q3vZX-Uh zHiUn-K}Ks8GJcfdjA0YBQfqL;wGDONPuayi%Gk0ln{}?efgZb#v8G=Y?8+2ndN`<= zdHa;JQqwNBp(FwkuV1mO@u|?g7>uJ|tJ$+=b-MOD3Sqy$urzBqT#QhoyeC~OOA?7~ zmddo{=>zuMP@Vh|mB^>)4YLStVJ~Ktvz(hL2n)Unt~JUHEHvtB+NVzh4b1fBySy! zPY*R{zikiNe>NO4FO%7a_ZnF7P?nCZy#k*FKiJbi1sYqZOzX{hLPb%L6js-<<~vDP zH&LAey~9uwl8VH3RT}<7MB|gLps`wwtoGG2wL2nGNDADXXI2e- z41Lgjqz0`q`jpnA7QJ^?z<%de^!-u*$?0D(H!X+V%?h|#HX-Xm6_anxV)yc!nD(1c zRE2c1O0z;X2j3^o^pp}+TI*7u#5YNy8Icxo|= z>i?NN{-RD#u9h?R)-X)a?@O1Bo7itZHPWz>rHebiu}J^Ntk?buHfYWzsO}5J>9gJJ zOh`EF#;elmV_8h{A`Hr^95M@pzZ&kdWVuN2O2l~CiBRwx{i6K?a_ zc{@&v_Pdzlz)b@Z0_~vi#|(#M3@9aa7)0y4)}VeWrDSkV49ICYv~;S?{}EHA(Ybte>bmg7No z13Yc*p(87y&CYM7i%wSK?;Z#EI2+LF!F7=AZ9%-L7A0J4g3(zmV#EBA>6s5DeH$!( z>5r1QN|1UXbOa0Byi*M2i@LN|r3uMdtw@CpEc~i){y{m~?=->ZX*D|Lx1wH@#h#5- z!X<@lb}{ifoP1BP6N4*Qhrcof+-qV+{YzNdCRr3~heL0F4l{^NLGz7Zqz|fPz9Tj0 z8;e07$#?dsZx6gas76=!d}nK7B2hF%nLLUfFuj%PR5VqYk}M0@{JHJy*3MFNpROn(>Oi~X;{XGjue=S3r{BME;y70C2U8nXLG zqi%UA`)I65-meqTcp(z)TU1EdCI+JW8l)Z3gIua2@OxN1i!alFLeCy#G2k+m%Y9)k z*A!@ysS5RN?1kCG6e(y@BTF_-#Hyw0H0W9=bhoFXgU_*Norq@L3Ps~Ib&85@V6r-5 zQs?_HczO)XzNynwTNN6frAf1oX;5IgCYg*qAl=@mN87hZXuzCR@Vlr->rSQ#*7a}k zjn7@I?G2*|fpet0;egf07D|f>X5+TQ4j7Eor5^4zD26@89`zTNPSvL`kE+nr`>I{dV!(bKy7Jyla8;2|>RDe|n;N?o{L*Z^s^43)IHAW7}E@t#GSH#qo7uJ*?oVTMZH4 zuMg@11bN=DB*zv3A7x(o2HDnj&0GsmCpkOTvrwb-1j4T{tnx z5i?ESVyvAd2E22_(e^srTTlVlS>_Oj6yxwdK8O9^!X>H}FMq9sd9OOGJWvUv^jh52 zDZuT}QvBF?o6)UAW^a6#eR~{=f*#qdef~?P)gYofwe{@%_ae4Iqng=UB_OFsE_>>k z0KIQPNWI?120!MvQX>KBQF$yq@-wUbrA({kI@tEjvH0~+i3(r0oBM?KDe+%7A!wd2tx>_}zYQaSr00;h1KT zfMIJiiF#asY*aEn7*?{}(_)%v9|6Z731~7>rF_e9csx_00Ch24VzD?N)x^kSG0gn1 z20fh?jhQdH*4o?&R7 zE23V@<#7I!Dz!5udbTkh?#`MNt)W03KC1NZof-|Aq(U{bDumfiPo$IjP83ESb%FZw z_E|p{X;b0&W(1VK7W|DQq&invGhy@9vyOn&+1BiP3D-c+!#sS5_&vuhj8DX<3n{vgsqm4d+$1YeDwx@u4~hc zzBTyowS-iy?eTeU30$1a@bB#!bi_8I*|iBf%l)ybrU90|5}IRE3YCXV&|Bn$xz`#| zv%eURmNp^hS2Y~!s$us;7C$%CFpEW{OvW<~K`S4z>g{jXgdlZV-H^rJUT9(2zS*qg zQxJ?MHnDl9qam|B9`8rqW>>n^DDg)GLKWV#pLUt7;;<|&tnOxylOphCmkPaizsk&I z#Pn>4n38|jvT7*dd2Soqvm*t@(NW0U)yW)Zg=1`iJo%l=WqYdKq_=}XgGq*Z!NpXLgnZ-X5#hd1`yW?-d)J=oN)yHDuig*NW z5Yd1Al<2u~GWs>B)2!p?*cN9s+B-%>^{&Y{@udefd=b$FQzh!RSB>5%XwdB2`m|}H zvY@vwfMdBe_dS_(!2|%N}9xX_# z!h#`FkTG_WP&h-6R64$+woI3Fqb=}Br3(8-4;HNS3Nfsv4Dkzw(T3YMrKbABXn~v$ zIuEah0Ds#ouCL4G?sm0iY^%#@3 z8s{1$Ttm#K4{Uw08eJZC17~(dc_6aJza%FtHt!QIU3#bzp^{eirCv79LMd6g?Z;A_F9?a*(Vw_GX5_UzgD7e%@LlgF(6~LQe@tg(ESt_X;94$qhh6Xi0_iJ z*~+V+cW*u_eDuj$t{s~WJ3wK90iF96B)G0GNA^z%$?2}dd)az~kI59YM^vI_bOox@ z%VxznNys`=LMM$aAgDNC?97YOGWX56GOZph{&vzOYga>MkA#NMN@OgzLyNW@oJTE! z<(5@Ye$jwF4)*X+_Q$WXztR`p#rQtT0?$Hhk?z}sKb8*YT360FPYb?2m*Uf23H=Gx zqtQJY@ps|^%qY>LUwj5T;ww;5rbl+0tI(UYX`a_QOxCH!`e}B!TWW_L$BW_ct_BO2 zZp7~cB~U#ZCD`|@Ld)VxOtp4{okcAMY^i{baS3MnG~o7&W=s*6vApaS<}{_9DJNaU zCQBu(9r=Mh&=ZsCoi}V#b}>8jO%t7$!eCYVnHlzv!_Dtu2%DJ0iqe(njA0mbY%AFU z`3{zxrAqNX@3XPDBe7$9FIutYBl8Yaqf%!D(y>Wl*0)kv-1-VO<90Y|ieu2|cb~aA zT|@FAb-KJimz`IR#gT(DD6xvg`!BIL85j+`jYGb52vWW%kwEvbY_ueqOBO;o# zJp%J@Mq|oaMf$!l5=ukW>E~Egiqwh6yxq6i2>fA7IIheT*^DPz82O7zN4f%bpA z#ugc?Q1-!kcKmHPVizk?KyD<0u0>(@1u;EYu1+Vm#&b@mNz;3^v9332)T*gQC#8{) z=80(Q!k+Zyl{)q7;?LnNReI@dK*QxE^u$SOlrYa5Z_gT#(ekOn`|J|P_C0R&cAJFC z3xcHg&#u5yM^kC=2^aj^?2ebF2BbQs8p%J*(0`|ca9^q{{rj*5FM8>aOa5Y{&VGla z5Iu_5sY1Z3G7R0IE*v=dSQxIUC=}S3f^M3l?Tdu^C|PrjCl7-;Zrpgr3i&}QLTrE= zLTwiybEFMo9b7Q!KX+`jDMZnYrO;Yz1wRdKN|9|tBjOf~7_voLJ8>tbtGH?j{sRcZExL`?R2 z#x51BQXt2FdzVzO?mP|39h%54-$}sCLcT{U!{P8d5o-ha&)p{`zdrHE?5RdUkD8f{ zjt1o^E6{4L+r%DKp^VjP^m>E}g?jg-5>lkqPcwvi&MgN{<~rd9Ux{y?Gq>vm|KHb(Y-3B4Qdj@pKL9GNm&(EenB zubR4~>Tiu)EzV1?~xcI3M1_LWFDz_f) z@>Ll0E1y~1ea^;@$YI@0i8ynziTS91W^1Nv(DijmY)MxEV@KNAwTYKdJ-eNS%SK|? z@MO&X(84PC&9%2n2Af*TCi|2!?C03My@6TW2}6&43Uq5*4%@X;mCU=vv@y4W>F9l6 z#`9#+V00h*{Npe&;2{$~Ooh^55k1)=3%%rI>>U4`6&PQ}hyJ0sXPJ)q7Rhip7Y@-+ zWt!`ofc7sDsPd|152Mv6p5w;mBMGQnsX-;PsSH%X-IMB6 z;}eg??XQ_XpSKfO&?P8S=6xzgk4pjrcI$(yDSMhhDsF2x%9cg*ZF=F z(S(j?OzVd-g*l69=k+km-6Tuy2FkSTEB{;#$`n$sM8k_3g^r`8!pz?KG$+X&Gejqa zhx_zN!JrlK)gPtdXLKkkP>TkT1z3xm;QiAI?#peFe^NpZRLk+IrxmU|)29{3NGhLF z3OUXX8gg7P(7zt8Z(F5y6Kb$sxfIvsFG`2q)ul}hr=@P$R`{A~1??LuMm@Xjp#Qr8 zCzQ0w=fgS_`s>lF@3v4)^+5jYl^9pK0PXL0V#|mctlG>ut^O($W_}ao7q&p9pC$TS z_dtd08#sqLVS!aS1_-rCob8CVANq9q-6`qQ6P38%>WHy?A4YuUysEPuH#TUKVL&sM z8*0<%Fn@elm5;tp?BH7zfJ+56$b3_T4da)hU`rYL?$xDHvm4>8(2iL{>@ev@HOBWU zL-yQ87`4`7Mol}mQ!bmYBEog87i{$4o0xa`2)~}jPE)xU0HQ*CS5*G(VT7Ev_U z{a>-8k*QeV9}M@!)vO{=on{`4#MtAXSl2drSl?Hp=#~H2@7xGny{}B^5g9BuLyd|Q zl;~V=5$k`uf%VL;WTUN9aQ^pId^-4mwY41I#qm8p;E46uaxwpNr!sU<;AC@!#(>8 zYn>%De2)bNKhYy0WxMeETMZOTbeK<&u`n-5pQcwX#tUNuD*EDug;gFH|80QKZ$Jwi zTkUXqgoMQB^(a)M6}xoxh>WeVd3ptA8cWC_vlJE{Ik>yRfI^1p)7?SZ6rbgW$%_dd zHF|V9&>q=0tMNI)fZDumaJi=n3yD|`Nq`HEB>3R;85>-B?TV%2nvlK5549_2<7>Et zrroYUvY96a4p|O=mj7`j+UE(~}0wJJE=W zVGE!!qY>wZzen?<8thD|!SSp{6o&KNvZjQ!jjLfPL&KoIwSjH1{=_uf#gwtHh8>Ll z$1?saW}BUFL5`I%AK7S}*$|Ch{X5y7FRJv~nBT9UpNtmXX924?hPu+iX7;^`(Oe6z zd6~m5dMnT(t`m>@?;-nXQ_GgWj%5?hM`B`j0<`DI;eU8@-z_z2s{G0nhXf;kgbGZ% z;y@*laQBMGVEG7){+Nh*&N0r)$H2cj5{F;OB3@5{a!aEy%8WlBJyb|;b3B&DiRc~I ziGH<(WBT6rEavVPCd!njkSQT3UHO7}zZH?XoruCaA2YGBh*E_T=4Tp$TZ=U4@Pf-w z9UTXAcV#NG)F9WDad_sZK?na-vw>Ay*A{6|4u2mdGgL{9>ord~&-tURN&34~DL01e z8;|u#Nk)%u?lHx`Q~IQOwOHsoyb0qJBqYmk=ag$#go~rCv1D+Ha3OvXGQyVOi0OI3 z$fSvT8E!B#X))TeTtZt4J7D3aOQ(go=*TXETGwf5;@CPE2Q@>zuO8p498>mqCCxqS zfcOJ`xYOWn)cc|bu1&5*)A#{KZDFnmc^WA=6j(z&(i#WN7o%gkCzfivqH|y+wnmuZ z>ih+G8mvv*E9>BuxEX(q9pJ$6+!Vu2XmBh-(abu`>*Tq;%AgaDq!eLM?7Ot$~7~v(1sqX*u-R2u9?Bcy1NSGUkhK=PcZ%PikwqAoy zmfvFE7pAisxtAFVm_o|}UI-WD?SiDF;k#B@RS9y4r@ zM6REh9M`{Ov->KOLz|d7v%A?bcMYUosA0zgQ?U1966SAz$GVQjp>~FtVgqZLQ+*`9 zJ!)rZBNNc=5{uTRcxe3yL)+Oi(0QOtMI+B+bZ|I+>d3+_lzTV-MdQ4DI`;DU|JgeY zhK&mJGroS?paoY)_CmN83hZ{PcFNa~d8xpoVV_26v z*qd?$>~Y2hO<(NSJ0xwYs)YV!E3^dq;GvN;nu$ifh*fl8PtIL{ym@)*TvR%-)F&I+%x(0 zh#gXEW5)kfi2aj+slgwnRq>OJZ;3#QO)lH^F9G$#<8fU_4)5GlC`u+Adk6kzw>o67 zvg-ER?B&4*c4})9>b^vS`Q2dAC&O@e zrI;SXzhURw;xGYESQ^(|;@o55+M0yWh-mz|6_3Xg#B_X2DvEj}K-8;}h4xUSm^qQK zC<#O5V^!J`9D)HBnv`j;PJcc{!p!P53ts<`owZS==~evBpUGtX*DBD)74{PnDbvR zYP-id%3XCD6QoH$?(_HF_)c*3)TVu-c1dq8bi?DfJReZ{P-?rg8N=iBXxd@}^4)tx zXgy+&rriUC);05S(#L{hqQ%mcF->rM;e-7JMuOSVqf+heQjBYt(BBMq6#JCGdX5eS z8Z|(3MLP^{>XG(!XX$<0I3aqe6Tj756YTyhm2X`KU%4voAt?xxOI$GShc<;hbVFE! zEv_yx=RCn4f1YwaVpE5le0!`uX^mUA=S!b0YsAG;Yv`+U@0e>Um)gCt?M*Q}Gpexm zwgo!g=u?wdvh?1rI)rq2;%~JQ%QC3PQ13z%w&;*$SR;;xhs+wGzXF*dHF)pkipgIc zFyQJJEE!OPPk%RH|DV@b>a<8`RIfz1e*@}=m zQ_6zvWib9!0ya&3#MG9RuvQ=Lr##DJIyo=c3x#_2aZm!>CRMOZ^*BV#OU8bMXUuuE zDp`c2bHb04n)qrs9i9dt3Ol9(3d<~m- zg&@c`m%a4TpyYrAl=&s%Y_^DoI7j00Tru?uRi=e{iMZ{U%%qnqnbJ{38f%<}DXwMg zNToU%tyiS1b3a(!F0Q-ymN2y`(eMdUrS~tBAiF#o^H*t5cCLurETVB^t(cs$J~P*G zB6?t@LdNcw(Gsabhxjw9sK9-d3tTTctwI$i&I&R|Ta7;NmI}T1*ki3S_vemqPdT87 z``S7}{WS^g8^*H_CSG`OLQlHg$p?4pd|{iMBrW?^0gX#5Q1(!dmQ0J64zw%9jYDBV z<{URT2E4&cF`r4>3S_LRK>Jt;C5-q__&jEnH2b$BR-f90$%~SVvRa+c<6Aw1PF*@U zVFA*H8qiW^3HSRexnJvqJ2IXKZC!<7kxf{d>V&=HT+rKEpFXsg;L$-3Y&_$E4W(5G z8MqTdzXoJ&u7r5DJ36(sDOY!yaCc@amKt$hBsQSWGjcE^x(P}u2BiJ51&VWZI5u&H zj&&WJ_bkCB?jL(RtU$PJ10=H?G5KdDlJ#`yTTly>{j1@a?Fsd&W{9k-aWtm|#)ld) zsjC*cmTfH0@;jSxxr7bYj)cPGuk3rqQ)b{SqEjOtu$otoS-U|q%Q%#b1&s~NWM(w} z^^S$r#BVJByds4rMB($ zyTu}0TiEyIv4~DhhMa;NV))E9&R3zlLmBMj;7|mwf688E#zX!&=M3Ky5z~}}UUF&Z z3|AtVWwF?49|PI0*UZC8k&+gLB0Bjx{8#VYh7CC& zqV||zM7EYNizx~;U!qLP&uiJRtr~P(=L0L`e$e*+MD$8G8aIFN{6$|Cy1k$ejhmZ{ zEj+{EQjpAa;uR_IuR6UQpN_-V#Wd}_GGTx!jnoj6b81hrN$-?;@QlL&*)u{?_A>nX zDYb*{-9~xRZ0C_Rtm6&q_$XtsWO-Jz*E|ReCv6 zLSMD3@sQ_rY}Y%&WK27TIC)8Z<6AhVEyLtX67sydS(>slKq!rJ!ZueAEO*x@j}Lb6 zif_O&j>Ep_J0M(>ga>QvP}8&;MI#;YJ=7XMdk9F5u0!ABw&-}a0HtGe>5_RH)-AJ! z$w6<_at&rt&sErMz_lWSLde*(*n}m~pdQB#jumfafm^^+{dY zfGKWbLDkv@1NT)!Ya!!)m@l>jSAbpQI?^L=Box=;49CwIFAFgtvj*R|R=(7(0l#{b znh52gx7q3aYP9NVEY{TJv3bUoESm3%v*j}A7oCI+{S>KhViW7leTe!0 z#PsPg$C=GaFx^_hejyS)b;BAn?=|7&WCO}yP=r743n3~QAWY2UJf&NQZl|t=dcT$MFCyX6 z98cKpu12C`n{>b4DxA#NEv%mAj-}16INQlRy|Ye;n71 zi5GIziXmI1Puni7M)}GTj1ikcllzKA*7*p`ZQbx0&Lwg(8%-bURv_+F1Cf{R2_NmgqnNL`e$2kN{5z~l{NK~nZ zqtac4+(T3-&?FUQ9FvOO^I55(8Y$isQ4?E0$G3HcX3bE3|2Tf9~5Y*aij8ojp_n@F(61$&vb0Xjg)g zA!`x8>W~nr$UWO9^|*baN-7PR1q}Mjz;A0@pV@?5okpzfX^X<%-ol&4*TT_7tuTnQz>qz9w0`18 zM2>I7r4{=0NS|jOi-*$R<-S;buLD_WD>;W*4jqLe_}wVS-SJL1&H2-@5Cv(bh-)-I z3o$v=1|~DBQM0QQ<&hQG5%d;QwiRK8MH@?>(#*y-*06g=HnN26H@S$lJ7b|ct(=V-D58*uQQX&i%|=a(=~ zHB*^RR+O<*d8*`gQbYq>+F04>QkGXI2ciEp*u=zP__qW$wmTm7XChyNXrs zQkY2gJgiiBR_j14JjZcA`@bttwBS2wQzQ&xlM!#$!IHSA@!(53GK2(#&rqYAI+tLa zq)B1<8l=em-Z(>! zh9+{XVV8(e!^PxXp+Jv&T*E{co`2<<_tZqbQ|;BLb9e*>oK&R$28*fhLv@-zUxC(^ z@VVTmON*`NNvn_O)7;5Ral_DncGUNs_4#Eb->(;>S3Yc$)|v%LMLe&p-nY?+Zrj78 zVma<#*P)W4QYaZ%;}XvdPVanV^s?#=G(K|uW}OGVv~x}%-64I%vk;4Ho6&tEMA|+& zSg0Ps_huh&n1tJ)VrYc0!P^`06RPp`%v+m;|EzfiB6PYQQ$0yIx>r0lNt=kk!WhKjm^97}AIWIU7vlpCjz2J`K6p zhFy1-z&mWc(5ha;d2lOq{rU*kTWer3QdW33ej6S*-l<0>dJu|XM|ZJL0v zb86Jr@*#738iqj!-?8o7^Sksm5eqm5SYdb-P4>}HzoJTsTys{K9Sx&j3aBd+Q3=;| z?uU(y-yV8-BTt{Rdv$)6%MbuH!OHq7xU+y$ch_LTpN4L4lDE|yA3_L9xaOz zBNa%`B!_j*i-%^m8kxUM!uHNsw2V}xjbbtNn-c=hIPQCoe9d<4Qzor0F`bwl54Fc) zI=@v!bMlp`I5lsORQt_Z>3fbD z15bOS-t~ku;F}W$wpv0q!c2Id{1$m`ySeUfK#$hy&>O27MBLgeEpl6qt@aftKBmY2 z9?#EDXoX4dsZxW+Ag)7xFq+-53Tyr|LpbN-l`Adz?I^_v&V|A+Z@@*jNrI8FJvRLC z!7x8xJnwMEsGk;CnNbP3oAz*zFoB3aGwp`&U|wa9{==7|(y1IXuG=Dfd=-?27a={` z6+Old5R69Y5v#9+>>n3s=o(N{Y)nuVQ#cxnQ`g_K5B-y1kracp=hv9hQVp{3 zh=q8Y27C><=G`qvQ_mzarC9FyPEaA|?_XH>3o)I}?nCZb?^yO@d2H(UmtjR5I=QwY zFVo6==Egwc#5v80a@M&e7P%X)u!9#9(BYMeEY7K$IX7wB8;NT?Bl!4f3Zgan9A;Fr zKQhX6=W_(|M#kX3KHT#?9fnR4)AF^dl*zT7?PvcnHZ6n6g{adw{X|@IQiMSf_ggYm z$W;9rlgKL3VB?=G>O&}exqs{t&-M8=iAW7mq>SU7A3u*mL9QZ&n>=BgmMKx{ZV^?C zibvuGWl~(LME+r1KjeG(O|~j6&DkRDR{d!7Q)j23{Bi@*mR&Hq6YC(z{%FPwT`kJs z`!d*ms`PSj0Os51Q}!WmXlS@#lbjCikS#-h4_mN?pVBKzhQh!8rFhXkO6rn72b+18 zxvA$9;oeQ|Mab8~J2OSv@7I38XvtQi#kmU*=jVY(U0Onc?s{naX@*hXIKd~#2ghQx ziCGA!zRLaLeqMMu-2qKu9{6~?0-qkPMA$K&gVAyo3SZ{KKW#w1Y9yG?YBne&L&;t9J4cKcfAwM)@-VFnCwY9~lKP|9Z z?acoVUE!Qsj3*{V=z8ylw+E|{&UMuvPL1%dEx?)etMN;L_Z{9=Kq0V}EFZ+e%^GxdAtQsZ~LU4?GZE3>_ z*}|h-BMiNa`#F!;+&DR?z0#zpf4OWd*Zr2LifM3I7n{WMC}Xdxk>3p&Jbdy!=W&q$7jqw z0#BBvK&!KX&6}%A{aX`Yofr?3KFahV|2%s0e#(5FJ*XTIjqQ3(tjmS#GShm~5x;oo zWxipF9~B7!3e>-+5^VeM_mtbt&iN-Jg6oQ{JCd<6G#;ITI*mG~KuMnQaPU(mo9o3) zD?^D!@8NpmxKL>K6O+dVF?CJQq%&$NbWvVJi`DcM*C9UGtw4Do+!j)2dRWS2yyXZHMn(@lr@bxb5mOG0;0JoKle!+BF2 z=Dg$iZuwgn_%E5~Kwh#x`l^)2a|Z|Yxv$rxLF*p|qmKJ+dN^`j5G-)b?8w)&Ihu`$4aArnxXNj3-TT6CENkTb5hb>^ zyavDaPZQcNdSQ?g$AbzE2>OXQ9;SDQzo5pl( zlki^c4ZE^55ejP(kf2e*vV3}xa(O)J78Ecyt3tMhe@CNtzhR163D`PHL<3*tFlEl0 z)kknI%&d!PegDbM=Tx$)qIjq~CcwQ+1&i#iAYiWw70CT%H%$`Yas2~3z9$BK`7V6? zEgV<(@SH8zcHZ-Rcj}odJURwo;QVx9@o<@v9~us94+ zR;L)QK_0ks894_zzwTYmmKTV~vW$21vXii&T8S?59GhINGHtq|LJyWJQG}TmwN5P; zv}*Mzptmh}I zLsXs!`;1qi%dZj#SA`k5Us;OJ8e0Vi+K4^R%y_rW1kwUa@L(Tak89^$u%*a4yA}7= z>hoM$9S&PBuRH{h$i3100!FT{>% zM2crCUURL!v!)t1B0HrIrUpQV&)&XJ-j}+^a}|O0n9gVGeh+)p=ar-E8OP6S+MvFn z1iRB6ai*XORTT{=R(=QF{xwK*Z{qvq4Lgur%)I`4!hZ8SpM9??_UCU7)8Kc|oomy< zgFmw^7B#GX9nU@YiDB+PBC$d<1gl$RFsqyQLFXsK^+YY3_9%yyH2lLW-m^?ny^LQY zm8tGa6N^2pMtgem{^ja+X5g31JmuxNznKOb&Iz7*J!eRb!>do4^uL)2PL4$6={DA) z6oVJ?iRdhd!?kHw@nAad=<(dwv+^WZ_U1Ec^O>!H2IBU2;^DQq0a2MxphXDlOli z#=Ac$*!oyR^OuO}Zt`UW&DWrYwkNF8R*@t##k6pEBvN>Xz%E8a8;^@=Mv{nz@}6nZ z(QKpnThq zf0Ygpmf(f<8u)QfdT!l3o{w(AX)9eCz;Cg=@c|+4uqPhGxFOlZ7>fm0=uY-QrDG{p z@eV=WaUXOn^FKb!-Fgg1AY2o0{=C?36Hjs~aX&m7ufQ8>x;qn6sz^8+z9xE8QN6?CeW9I0vBIILKM-!T*XS1_242FpR8uNvQ^B7@1ytM zWT3e}3A=d~$Et4;TliLuhVcw@e#lei-Lr}1zPiI2`Te`V{lP19jXg7anQ1ew*)n5)aFpg^1G_Z zyMfy;@Q&&Go;0XZjpTPG!nbEL6LdOQc8Vr>riSBW72kne(+;pzrC0m@vX8q}NXNFC zIgF3NR})n-;IqERJp%1(MWkoWJ5(ykm{Kh!!+mjVS%@+%=X!kZ@^~c8;#y9sG8yxZ z>ZcUmQ{ny5$CqBt(v?{*oSDyaurKX-_pC+8$QnJ<(76h;H`N$DnVlTc*TGgFDTei5DSje-0 z98(5w!HtjAV6#pNEm7Pj;yRWA_ah}ab(j`YimcZq_{6=Mf0KAVH})l4(D;sx$ot7I zSft?eJ0{CD(BDeIU?967i-dvqbMk>+VDe){d;1>JkRl(X%M&NQ-A{y65 zv&`++@JPxv^WjD8&l8?C?CM~_-toMD6N!d)l`iwVL|`VST!_KGuMb&~ zzA`z6hokHi*VGRyQYFs{ojs~SHc4DdZ;0mI!fQocnYulQDK!3CrUe_1z^Z zB#~yY(vKoa&w9#sAB;t(of0j$l7zk-m!9GIZF}Ag4cx>1Z&eLCRb0z7S~(Z-QlmEg zNX$y)-cY3`1qvDz8mL6S5A*)=^eNKCsmanILnRb>!VIZRdOS~gPFTOW2DAR^Gxu^M zVJG*GWos7V>QVzr?(2;MgS}CtX(h~i#k0-(9Z|s~RJTN*EDkrLG?mX;rVV;NE<;18 zgcK&0V}orD_g8pdmbADr3?Sxk$0<=AKsjZ(K?_^eE5%-ld7F)ySbx)S>u>$7% zo#D-O#=Tv(_%YiHcIO-6INc8=YYFYW3@BlKC0aP1ELyk{yWcgSt{3llzOTRtj{6dI z*K*vfAT>X)LyC=Wus_HPTa8VnGs`QH>f4B~ISzvSh*E66b=4@Pmo+x#@ZM>q1B9!b zn-8nSxTrRy`MF>a$AMopBy@;(>kSGU@%Zuryk69Z;2&=>|2NO~@_#G3PZQrSRcyn; zlK<<6Cbe9_+=FdwXI2NB@K#Lzb86WUvp?*dXEA$}brZG?#mx64?+NdXf|6=C%jABt zR(UL{c%NnTmHX_)Z&lJRs9_^~dG~mPI`zyhWP3iz)2>vN|I7TnSF2-#$0V{)9iH7< zl!W6~dH&Ei0iLz0B!7Kq*N92@w>ik*m}BGO%l78C?K&~Luq zf7fw`ddJXwxJiS56RGTi$wRfNXWmI=lLx(+nl7X-3Jl(Fo{5zYAgGy z{)uTfN%7jJLGW~X$&OKOp@}jThqh$15no9EtE*s=BOxd}M}4Q4L8R};!nj$EcmGl4 zd*h=Jd4@U!l*`m0FIw}P3Wt3X-e03${sI+#$P6M1 z_)9t0yuC3YBQn7qL52Vo`3S#IqLDfAbXL zl+a8vY>$Xf+mL|eF>>5GtcC^A%=WdNbjU|x=(~&Z*Odxfk#y{dXXN=OCq+JvW{GCG zi9(Qv9#4@G!)Zc*I9*?t%P*tuR1l5^!0+XmIe9b#yA9RJ|{Dm zW(hBB%{G}kVCs4y*{p=RqKd+l3tMsLa2b?x)dbJo)z~Adz&E-ZmeO}}(%@0TQ+hA9 z89Tu(eTd-Ddp+W|NpRs{qxhc7O4ySRTskEHy(ig&ySgATdo^yE)4ZG60=?w5P@$Q5 zdS$WT9ZG)B*JXG(Xf@j8s*pL(4BO{d<4{;9-kx2GamyZy+v0|Bb+7lh>tlxz2xC= zK^0cq;AkbDiGp(-0+&)RR;d+r*6-14-h1}jqJZ^VD?-uyXy_c2hVZVId4C{g$c+IBni^WO<5kG35$t2VtfOSSQ_3tN1=zhcFUtF=Vd_*hXx6|Oj=LJyKrdN1ff||o_Bi%A!xob zKS~;Of=vuo&TV4juXVHO*7Ce!LK^e*DK8DZYJ36h_m`Ga-$743Ocwf;4zpb13vvC?i`S;wzk#`LU&RmkjL7 zEfX#YcUS81&{7M)v3x1{bC{qkI6}xuUfA-0jQI?;&-vnoKb0trclDD`2~UxFI4R++W!U zzvz`{`RfLqK_hu%X$8zFo25ft`uPnK1jTH@ii>5qCsmGsc~00jPmAw<`%I8FmSCK_ z1Nu|{=GfkMcxBOyGWEma(hU-vUaZS|Wjj!Yx)Q@rt)|ZGTADGckUp~+zltpoU|Iv) zi_{Ht{)D?P=={@ih4;{Uv<$C7n{+)Szsm7^R|BH2bTU7mZ|w2)Y}R*o1Q0G^BkFIl zX!7N5{l3Na46J8QtlzU?qmn6u{h1wI9tQiJiMa8jn%T^m*ppw+nMZUMV`G$kvq;zVk>0J*#hqdw)H!CARD65Ec=z6-4lXwZIlmqTbEga} zqwnhY=0vRNR_1;))%kJ7t7!fClB@oh@eWp5#k!RpkDi$rwCPkw;QL z>d6S|t2nCg3*^zF5bdDEO=L8+T`<0m$F zMBbQ5#D4e`tik0UI=qp(AA+GhR`y;gY*W+cg)2&NG^1TO_1YU-yr|z&I!rvrsSdSQ zo6xpdhhKE8G8p<&hx=S`MCAY@7>d>jM*c3?s9TQu7kd1+jxBDq>+w$>4#b>Uk3$9y za69LMN8de=-cg0Q^K7yAi#y!f>*jfXEk$4dRqz-{oj#hW{O7wtwXhCfw{~EKlr?>e z^Tnp4)P(kgW@Ij~#;Lg#V!@*tmP(cAd8^I)I_G1H{95tL;EmYh)&|f0n~+#XeY`&v z*mJXnI;ut}C2qr-E*)-7b5@8{J(3=pV#>_|Y`a*AOy35?C$^!Q*bhZ&wd|8uGn;tk z4l|4i#VF6$j4L)U&2mLP@N+rqThhv&9{K14nQ{d-l*hlQib8rm8KmQ! z)$d@TK5|G+Qs7$D%iDP+0^<`%CmvqQ3>%bqovJeL@qEc1(|qx{rJRi|i$>U7(rZc{ zv8k&=A*ZImA7>OWdFL32GE3P0*idARh{BrFu`v1=ja^^j@JLI6UwwH4|B=V*8}ySs z9wy6wt&7HnoM@~)qQuYtC0(&mg|ClL<@*$f1+lY))x79tvkVmZ7l)e&Z-32p6iD;j zKEy&9UBh^P^ogmcyr@;4whBLbgIj%te z-1wg6{3U99F=>y(b!h&1tj3RsmH6Sl`g{?)CcZF`I(J!aq+?NUlXwT$29u{1rpG4* zQm(nAQT(xN6^@nb@lp?KgvKqwmD+tm*qLV1lARz$XIN2&I{SR53~w~`__pxP=v1qP zT}h<)pEP9)DjIPub+^Hk-KWLt3U&G5a}L|;n zb+{>I59~3!?>gc+dEoo`g%~!b0ykedK(osp(e~GbAH?5UGH@#lcdo*Ssy6s!EQg_K zIX);fO*D^cK4fPeG%+o|R@U!wJZ48!u!53AL_7*b zJ>?@D{FL~)198X>=nJpLJM1iFLx)VRV%s;y!k+Rh4`aTu4azFqYPKTpHB1Vsq<2?o zJY}1=$3gDIb*Ogth3HNYqG^s9srr^h*Th33M4CF?(eNA`gV#|}q^aJJh z_)=BRtO7)Q?E+#gooHY)|H^W~uZb004M*5eb$;l6D14q%mPki|PyeFC{fP0@mvrg? zNinO8P~^t96?nn;aGa$M!)F(HKHQAnV?`x?cfJbG$&U~}(9NAED$(Yp#};B^sScMm zUm~0he2?pa1B4mNM)HsQE{UfoS>b%6NYFPmLss@89GgM@TV)Ntx46P^hYpVl9VfI9 zU+&dJeZHjD34`gr|Ex@x{~G%a2c_Sm=(7(0MKklWgR8{S*|xaN-C#3Tix2H`!(y3w z1f1R}evssh5z*RQb}Y@sjZWl~*y7JdZ#+r!!MXKS$jvgtzBkJ#_x?a=dsl}1c0U}b zHN}HN66j1^i_!ZlvG+JJXKdWDTVIb?FQ<-!>U)gdV~c4%lZB=G$}mIzJyb4h@*zV= z$G%5*)<_?`(Wu3Lq#e%6G=)6%8T?~Pai-iAXXB`QJ;+1sq)NFO%4__(YJs(zEAV}8 z6^d^*z)d8<`C}F6XIstIk@qkoO&L$Zsh4y=oy|WJ#pXkacP)}b#q1JRbf=Onpq?;` z{m0DILl75v4lXP2GrN1z#KlO%$q&@CFUVptw?w?UppKPKi-Dh-9KY|}$ZloJ@pY7e z@m<=$_M11eKedG{=0F%)mnT6@?hT7q4MzuQ(kn@$)R9ibqpF|Gmhz4Jn^IskC=t4w z;!s0fmLZgVo~4NVFAk|Hb5&J4NDLU4QOcq{zE!l(_9Pd4A-IDwp_a(eKyhant04 z)TgI8y~p%!OLRP| zh8yvkK8Msm@}eGI_arc==wKl-U)hcmP+lb%HjP8)C^ZW9>F7mkH zBjTxxuC$1hR#G3jjx2GH5G{c;rQ=M#4!PL+@A zA4J`;hwMJFKPM?l@q%ZR>Cwz(-xo^r^6M&m*VGucbH6-4w)z$`pxoJx3v`Yjr}J-S z1lI46<+n^J->@d&1lg5fVv2A#4Fh4gJ6M%{$ol&_3*pnKUZ zU7kRUxm%``L7uM1Q|E3L7cQ&Bk!||aE7j#eGy4k0)J6YxR*%2=?0`9jZn)E=#~o|x ziPPeWr<9?+JmREy)LQBxm!A`U9PvQK=~}$osm1$$DZ%GZ367H2`>*|v_*dsqLI3$m zIA*!x{T`8!K$!@K{pEPMkWvLDWUVpNt{U9%NkpKzJ8qpo<%p~+YVn$F|>teb_t%2Y=MT66GjYfhP3%0L3Mrw zV%}7s;VC1nvIYjE`PjMFP~W+ZdO?jykbcS@p7_Q7wHC9vxv^-Im&G*im(2gAA|I(y z!|XFknarO*%yvNt4n$S46>r1QcR1-bH6PfU6k-Ctjzq<*S1jyme*f%g;Dvv%Tj z?IOZ|Ea)0*aS2Amu>!n1v zK6=G|bOmD^y{C7i=(`*qgGtjvA-zkAXPHyB=W7g3A1z^3LrAx=PK4f>478A^Y)a=^ zKO4&H6C-YYVH9QI%UDB94|ATb$Up9(bF+^$UOFrA@E;<+@pmGtsGxacb0Om>-=My=Z35n0k>gEtm(01Oz(u5OKd1;pL!>(Y)}g=`XUp@MXXSa* zQp&^*trS%Cqr{t!+%Y&_?TAe_I^44NBLk22a#Y)$6NaUKFqnQ+kH>6xg+;ENc+*HL zsC_Vn#6gh_Bz-rha4lw)Y4NRcq&3!&CUhn{pt@~te>ys zGoL5s%I$0%pIwQi_x)gViMj>3#hBZ_89M75@a=CI{C77nXPejTM^q#0)qNdC*V5T2 z_g+{jQsT7(a@duXmF(ZUx6H;r0SCK^nZRRVM+~I!h0V-wDseof-$dcV4{XFUVknV5 z7q8L_TOYVB*&9v6}kDa7Iq+5nU6CO z;hQ2cYux4dLh7uxk4}X&bsc0^%ky*nsgtFr!h_q|m;-TqrUmxqYh39r86rpBW)asl zQ{*X+Rk=ImZ5pZH^u%he@ZbE?;zL6>;?*rp{v#_&NZQeeC+Fq~L!LPb0YkOLm&6wI zEuAF1YO%&nGiUTaSu8G7CEkYH3S9715dYdgoR4}+Y{0j=yfw-L&D1wvHZWX#D6jzW zMP(>hHiFN1*ISt1vQP*kpCnl1jAfff^4%j1;W{86ZMYSEFiSfj+*B{XUGr*T=rRvH&}hKC>{U>E<$`|E zRX9AL68ldY!$PJEnxx$?G%trB--vW9M)09(5`b=#E%h@^gVA;W zCkwh9jA((rZNtjg1j-$%I_5ArVsP%a2!UT9={WSWA)JfA3F_#VlYY4RQaXCvha#zm zm?q@Kr4NWgM`$0uuQn2Y9w>0Xol1O570o3_)o|v(0~XHJdFh~3>{^=7G^xwH+*pCP z%%ToMn=H>bl+9dDML|YKji=D}|Mch}yd>Y)^EheK7enxa@)=|DYDw==;cvp!`DCL& zVqsA4fOtJ?K^?vVI+IDi+4xMG-yoe&YlKLwKhq6|r}kpm3$ui0wl(;5U!OloULzz= z94P3N(s`IdXI6*}20ZaZ{vv(4$I$ct=ZdPsdi=Vpqj1$MAMYy5=2={DL&%(RRMH%f z@>YT^(>if^wJtZ>uEX!vWQYe97~)}%A9eC{xXF}tm}K*b`l1_z6BE`$M?sJKememF zh4XPw%?JTmOL5s?1I+){LLrb#19Gr*0{+LYt~cmn#sK21{V+ln>EVJE)u0ASk^IIA0Fs)&J9kK42KjW2tAp zGt+`vHi-JbPJJucn61S8tC8Us7kpxi+MiS3ND6=Kq9Gku&tB8>_N)tpbD9ix=1TJm zx^XCfTh9#rinJA>lU%^IcSf^a>z}d8wXyhn z`UZ{^KV^Oy(KsSe;#z$Mk@gfy%;#5ZF7at@v`0gHg?OCACG0&o3U|Zh`8b}AYu<^( zz9PP69QB__-EzF;8PqVx%j8dwAb&X}7(4nBij((Mh##c0w8xo#twnw9b(D$d*5%oh*&4SZ)Zpdfb(l3mi~Fq{BiOk%;-??w zWyT&5%jOaHGI9l`9@XLwYaB44)q=bPbK&vTDq`MxQ~qGAP>_`@EW!TtSpDxlbiA_1EL_&=2DN7A>eKDa5u3`GN!o7_HNSSLprK4{A>~JI9P$2tvTpR8f5>G<(NFT0H^j< zV8f0~_Q0})xhM9dF^hRazVFZ! zsGC-^s6-VWvHS+!MYOW8>shSedw(7f^P7#PcW3+q87^3tu?ksvzEDSrAAkOVozwcq z^jaP;tD9jkI24V=+q&8Qd!Z<)l;&xr?^)rpSn7G#u=>+62)ay+kLQtasRe=-ncIK%VnEQ0IO;*M1iLM&| z5fp{axNf#&MPF`npK_5_#E~Lj&iNdj`-fs7NB7s%Q-PGdjKDf#g6+Jl#2d20FlLfG zpFgLN9ek?57naHM*#}ba`y?@N>ApB{lN@hNR^m2Yvi!N?RKb%Nf0=tJA6VgtYB@Dw z8hODc+69O+(Bc2JXz`beiOX5!iu;zDJcjuahn}8!)&rqFtQqI$?}Z9IE0f@~dFBn} zDBe~hK04X~h%eaw@7d@UBvQ=h6*S1-pz|Hu*aegYarLLQfOwRk94== zj*uwc_0jJ-eV$vP6huD5CR=HQ8PtjVGYp+MGSciA^ru@}9VjA~& zBA#Ye(XaK;8c~lIKO5oFdl_CD9~SyLZ4}S>R|NC8RrqN2KemJfha(H{kvjDczBfXf zb{C9v5#!56Vh+>1)n@60vg7p-53a$tOEimVlwidb(vh~-A$ z)FQH1C5&^5@#Xk)Hm{+9^$L7lz7>~w@m3QoojB&+$5}rdBzg+BSMYe zI#x$H)W7UiuMQTk7?0|ThkFN(5IiAs( zDULzTkv=^6whT5a_VH6&;?o zcr_$+MqT=;$M5tm#f$}C#mS4+1ux%e!sf>-Xg9<)!MoHRSym3X-t8t7m)BzHXGhE# zlOwJjXd#3u6I(ifa(^Zki0{*m*TkSx%5B1e$r9|`uE+f+lYjW^tbzGlYhq`w#vv~K6@FqbWXv5$=X~! zs1Ri;2eD?a5q9rr!ttSY*t@wN-}38lRM8$b`C8oLag-oUKJa-9H|i);rk6Aqv$e%Y zyWdMJJG&lRM5l!r?bdJ~U5|5OYeZ1?HNv_a$&|}=A3^!pR_f_lX!G~_`B0&|m zp54e!l*UkgwTisASd1T=ghjh5*jjlto|S!rG_q3W8kxq-66N>=!|yD=H6HuID35aM z1Nnf&KA@i5bmwQx>Bk4AZ*hZdbqU6qbHwLL?qL!4;$UAc#}U@b3}_BGxbHLjq!^C8 z4++o{Ai>!v87qfMY#u(+p)^cUKk9cM$iLIG~B@(GWdP zhLxHc*ZHHse~^!P@EzSZSN&xhssGaJvK;r-j>8@DxexA9hlpqDYV+B%^muUII_x(;CVsYj1&Vi>!)U5L zpLCe|mRS}s{H)2BJ3kU;{;0sd^Ryqr%o;o9((`_xD0D5(N9L9`TqVz1lf1o=&uRX& zv_Y>PM;uGg;xlYF;V!XNm#xy{x(ls|fk`~|`!qLibHn#F>xdh@9rb>0)ZMFx<~r&- zm73zp<9NaUFZn$~+;DHH4IJF4H<#yt8^kMI{8@r&{cXwre=c?}*X62n+7No&5fZ~- z@$J|u?9y$Wa%rZe| zUOgNQ>mmEV0v^@%=xU%W#LY(B4XDP;4zXwBD#DPvX=gV#98eFhsv1U+*)!A{U1RQ&Xwy{+{h^jzJq`xBC`6V=b|% z=s&NXUAqtm{V@p;6H_P4JQlyzVsP)WJfD;qiJGJsq>g;Syp+}WOr3bx^oxhoF3JX8 zNW|M^N_>2i9RG5YvaxAxEa_JVt0q?L70P~ZTwczsJw^Q779}oT(2Kf&ay)ZkE2}b& z!9;Bl&x(%(ex$)lN1m&$P~<(tiP+dm%-E4#Y_Fq;8%~zzS0+VL54=CWAF9kXtdzJj zoyp@VU#U8l3y14;d8T@#!P0w9xSg?5IA}-RD9?N>C0{vog%-a%OP?P*?ShM~BYEUN zU!({&2p#lPh%RbEWbJMora8sIS4BK%c{#>K28jdb*%5cR22Gd4#G7Y)!hvJ8(7htd zG)2F~de+_ZRz(o&eWnHWH*6N_g4g5d)Q>Rre;_V==!7l$6T~@Vfv)wgXz-$UmAvSy z0e1NEvz&Y{TO|E(N6;A^{_kBQ?O!2p=cOk~93)6jb49yZJ0=#4gC`K%Vc#T5`c+G5pu3EmH*URG%hG%r;kslb^9-nR+(+`%|`vn7_l% zQ_yc0M+a%{$)4_LoHAL3$B`TNt#?Cc|F7lT8XHKA?Oc&MNf6<8_Vf zUxP9a@GfQRMg?QTXcc}jz#8sQZf1Bi8pmfdhsIQN zxytkMjz(5W*@uD|_t{A65cuv-g%@RBmM=&kFE0V%Ka{u&u}IdB2}4r!UABO_@0)Vs z;ZJ-C?JQM(dQ&`N=sa#Xq{yQOC80*EoQXD(K2P~a`MdOv4p+fkV-deSL6)E1{DP$p zQs&dV%2_}>>4qCc{Bm3(3|h%=%uwP*gXw-~7lZE(Qv67;EEd!I+umQ9k6apsng%Hz zv_^^lCXYy#*bbX1$M{i-IwTtHLWDuOSmv-J8b!MNz)kX)&(ebSd7?FT^PWeZHo! z4cFh4V2fh4LC8pb?!B}_JbbegTKk%#?ywGjSV!~EmNKlSS$sx-Gx95SxNDUuo;_ZR zT!{nfW$5n`JK*rIYP3cL;K@h26Ao?^mK3z%L9z>udpTeSapA^KG$9^K3Elm=knqtO zMVEAWq`9HNv#>m*w0WRKhS=;sI>;lhK-=k&-0X29WyXnz(r*zC-G7U=ZAMrg+?{K*B-uSy985%2YVvchq8${gHQRirX z39&rSj(N-sska%WQNqGcMdI@t1wLeIE<5x}iTfIg_@C@1W;t5~f5S&?@0>(LsR!Ym zOFgTVi$Km!Y3^YCjY+&{KLGVkOZ=(trxyXcgUP6KNx}`%5F7uf@HM3=7WppMx zsBp219IDI}_!8>fltz%Iu1wzJ-2}MLO~3&M;(ZeLNzXDErt@X^p6PW=K19T2-l}lV z;8YZ+O7q_X2k^OZ%G`o{6~ARF{Jp9+H@%?GkEYFMgB{6GS}*sQhm`rjdX9Nv4T7;lTB;g9Vkh`rMg)!%n(S%|2j_2fDRb z>w8!b*%d=)ayee7X!HJ*=RPoUINu!Uj;XY}!f2&7Z)x00ta_RYQuMfLfGt4XcX5z6 z>^m)RWy)mOX!}7_V+XZw5`4T*@8nAl${(&4YlP*a-k16UJ2v6G{d=tRA*M~21pD_C z<80?%+T%4qU?YARys)h&@5>rrzncoT^zyN_yc7#o$_NYIm167gxk6ABbwO7b(>=i$ za+htfW*qT7*Og-aFasRlL3>#a=+*c8J}@+P7BS7lr_H1pia##)?E{ULrWr{XxE1r zT9gl!4uabGbL{dkX_%=}&*RhwrtF^#Ey`&%Ur1(|ixl|H-hc3ZbRjG1xX(iCs@QIg z>*)0<5?-4`bPilYUJ@~WE1$D-Pl#jlXE@x+zq>jv1w%TBp}i~xnNmr_m!-MDClXS} zZ=z^S6U(NV*7a2^9*>X4_&U0yDW^a=hjy`1w!4OO_KS<2vygKQEZUoLX9MCeu>3W1 zG*IEA9p$;tl{~hexF@OcN7#pFH{n7ZgMaer7*-vKX~!soM!l&O1~G`#Q{d+%Dlp4Z z;frUj^dj*OzmvX0-p%lV)O8-K%cnSwDyUnMN4T{H2R1h0`*2Woz-gpbkC1 z9PsmWIeGY1#LafY>SGVZ?W6Sg(IN@;a6O?jV3SxlM6(8EZshzQi8b>}FlJYT&}*gv zCgiqa)Kh!(EHy{V-3I6@NO0_>6X`j%_%b6w{DX3cXH4pFdawtM(!aC+RD})1p-((f zjxYntlBc|7^G?2I?Q>qSJnA6*-P4CU7;)@xhYCM9@hwwt{J~a#&t{|VC&9Q)8XD9U z8fqMl6#kXu$??B>nJ^aZ&q>#q3T9L1O`IXJM za*uVAhMc@70{yp8_POl`bLkZd5Vz2jd>!qec*ITa4OcPox>}>q_ihwM6ow!rJsi*W zs`E>&W# zqyvNuD`#_u%5!a+sYU%iF_~r&kKR+v8X8j3tAu#9|57o6bo3bVCN26BH&izs%?Cw% zYim6-{vpTPhY%B%{+wGfO8l0RJRiMH!~=LCP&*PphhfJV8fAwz*rtIE^agM|Jz|##_5@nCjWqN$fxyQoA zPn8G@9w9WUuEDDI3aGdjiI?vz!Z^=z1Sjb8Wa3};iPh(PpEWGw?D1<|g*esR2@3-& z5q^O<4m7X2QQl8O-5I|x_@H~c9S&C-VAUfZ;?Gvlo<}p-b@{;UwH|Mitso|_1D=2M z#LX>L*xv}8v!rg8KjnP9Tre=yODMG0<@clBBTdB~pln*JdI@fxZb#tU)AJrHe!$ZV zUEUS8o$_3yd!~CJv&#dw(@QWYq8jc#)YqO~2Hy%g_vzg`NScrJ0RvpU-GcakmC)K( zPWfsHKAf*ca$r51-_jcjhZ0zZ>J1FGc+OU&wy=kbDBomW%naYRFzu9$6mZcMZl{WvS;it8eTrJP-{pI-0 zln2cA_Y?MhUm?33KwPoQ8JJX?!3NoeVF3NUnK^Cj&H6;VUsT5=`bk(}oQUy5B2j7= zflo0(a6d20&le@qPPG(>10FLA%BWtTj6@FYJh@g%`TTduxVw;YF|_aJc2)>=F- zTz@Easl7G$PHzQB|I*;^4=C};`-yQ!nZtuji6`f&aCgf3woy)V>Lh(W$6SYYAg&Rj z16QKNQICK8s>j2|wV}sUpWj|2Egs^nDD3>}M2s&F;rCV(EG%+CP!~Pp%QQodC-&?4 ztK#j2gZQ6K?HD0Y9&G;#Sfo~>?6)rOKa!r`;wntZC$8)mExzcgiTLAaPb`-9!buMu zerK&E9+Z`0WU@QnzP=>f`nMdd`)wgR!3H7coQUObfvW4x&@FMt++-7EUZC@L zeiI%J_kiAojVSzDjvxOl@nmN;zFHPzmzxLvr_Ims(c>!eMF`xwAAf_w#X3`(5l%ee z?vEn2=5r;^xc?`JX4xQNfCLK$xMTTMC!`bO;7k3?1(hL?>WIMczkk?jD`oEQm53?Sm9FiPm8HuE%Ifp4Yp9DnZ=-NAPnSDWo-{CNBQB0UXy?7S0Fy9S&k)UJ>qMZ7eMy~WdL?4iKW+7Bkn*AM%>fkGl^L|=wsJB z=N1#lpYg!AjH}{|#Wt`a#)oOqUGd)C%cQo{GVSna>UzjbZ6eLBiFEB1|x=&Sm~-lvkEZr5$}0OgXBa`4Q_-Q6!7@ z#pIR9s`A-JdF)NCG;f)p#$QA~WXUdlFy$8I9e+g=b0QA!!r!u*&=h3*)4aSRiXGgV zj)ft8P;n;;Ti?XsuwgtV(z}>;K9M|hC2sj93ZkReVfZ$dDa?`PejDPjhdhmE6j9zL4kfw4-9?7705tjq?8vJ*1L8QIV?FU;w^05t@3jKI zrmMi860f528SSKy&>qL8UE;(!_K2X|*O`Mo1~qdl;r05W!LMN2fjcZqu-7w!rh+Dq zUuuPJxec(tbV)oevzqcmb~vWy4j2j$`0;5|m4kNj$Zd(w5Nr1{n{&IUJ#ojEnd2DfGz!_vwb9fsBr z#o1!v?GoILHA4dBvudB{^Sxi1p_ylm4Jp>BqU^WI#7&5$ed3;^yG+?`jrG(?O&7&BM zK|MQp&9lGR0VSG2$mjdJxDU!N z_knqto_&jOqiurGqLQUkh}%L8d1@=}hAUc6&Q3SU^k->1y5E*w9OhQX6|2aR-( z!D8wjZK4d;jIIcL?W&<1ZZIxI2GL$I%0paE#)(Fn<|*5lO8fUdn1tXk=`9;*=Us|v z4Az*%!ZlsQ^J?SKIZecs$(xy@7e(E%3Kl*#hdIxY<0nqXFCRGLQG+qUV ze98Nn(9Nu7-bKP0HJ&3Mg}wKJVMp&*Zz~nvNqOHhNwPeCNjm#!rp9xhQbth}gThIQ zd_@WEJNH-M>Z?V35b5IO7ySgeY19eu)#6u+mts{5F)C-$Tswu>L#6tBe=m1oe2fk+ zPIg4G;b39%{`JT@;X-_)TY`;Z8qQ%6-iCvbqi%!g!ERd_Ozs#)>U}xXO5w6HR#%*O>8V@jIl}<7X^4=;Ur7? zmfPT|*)p0B)}!=e4dNG4kE-1q%CQ55UOg?iue=Kredyagm-+w`UGZpAF~0eKfOW7X zjA)j*Y(`x}%26)W@Pc{MeX))oonx*whz?jTEC{c|C1V|an>x$?yEnG}atEe5p`oS* zc`IAtH{FZ!Y7(^4y)rzj7CNI zR{Jc1^18QKx3lg?cxVU;uJym6>y$?MZFoX)6`CS6R)PMOERvk2L17^6@3Ddn$B#A%lp@i|-mK4Rm3cd+lxm27@*+R6A|Jf`?J zvoQmS=XzC+bgE`nZ5D|KNxAI2|8Pd9HM3g=osJ_%G4nH%m)#b-$jFcgvl0w+gf_ z(&NI4GGbdcLW^{noFaKvYBYj7Z&?nc_?UX@WOTT3SupNHZTfUg#hpS!uC=l>gjfxSwywf$kp+gdwb729Wk?^r69(jG?Y zzQGHdH#DH{XbBcBb;QVt#Po94=Z{_9BV~6vj*9HiT~~#|DNPvjh;}ynRYP-A1MQup zyI{WtR;XUU+zg@-tXIVJXTM}JLlt;On;cXQ{$T1&rEK25XpGp9z|2F!!S-H--_PFI zl}|Zq7s`0a(9R{Jw=7^sZ$31b_J+?3#GY+(#NKFQ$4LKii&5eFFUWuHe~ZnPm&TRc zBxIv)`@ ze1y2GjZHeO#@}ftpruzL>_sZv!6Fg8Y^k?OK3?nDYbbpzVM=YC%rTR`#r2Vx4R@_Qrup*hIM|8z-whiuCVq7W*OeV8h;U2ZKZeZP$-lsU#Xa%#kL#C z@Fi-b(S(K|Tv45G8Kpqn7iB(dl86_dSK{52-S_p-<4XrqURuTyL;Upk@xOP311XgV z)>mXfnHR;~nOZz@lK@e$KF^JI!lJZ|_+_6V+*PiG=R!C9n?xBaJvD=aUuuwYYn$-p zup2g{m*a2IZ1Hn>I)j$vG*p_J(e!FMh6X#Jk9) zxN^A)DHljnG2Vv_?{xXM3{C!OTq$_aTBPiJGH(%aPf}^VSaed4cS)C_SN2k|d4d@_ zoof(xWCQK0^7@}fj+3Jr@%tR{OTsD6`$5Uj^{vEEZ8Y~dRvzVL1x3%32l0-ooy5AMnM)2oE#9i<+} zpeU>gB^Cp9pjMPdV*cz$Ea3DVHf^IkAMx`(bD@l@$5lmsyR(7CjHhRqD9?Y;d+_ys z1zR&Zhb_2p6+zR|a3$#6a*l2FX_i-P^A26i%!I&{WSC^8Sj(Lo{}ygLaS>7M!Mn*x`<6NkOm^4Qk1bhc(n z@tFTY;kqn~wGX4S?x7r?7C{-e94RifyPoZ6Pr%OaD%_MZWDeWoVXH02{nRx0myAHV zw<+_xE9#lpo^pmEiv0DT8?fI_x$ND zBp<>+k53ad;dZtbkD1pgR$6N(-rZ`AfcX8wkd>aauhJNe_fH5hA1W~;gZ6@Zj1vNr z*Nb(YyocRGx=+@w!TrJxRB7t+-)VV>pbWhm?U8s!T|l!>BYFDAbtwJe2=mP<;xHp( ziF~Vs$ZL+cYMcv(732wi(VMV;mNFP@GCBVj?8LrA z$Z9mR$EU6myM*3JNj~!mQ{p-~L0H>b#e#@G(tVVAz`uG}Vy_z*`JX)Zo%oqq&@PfS zS#tb|W;;tA-pqCm{K@X;hr`%93Lme3X4Z|w*36?l+ytChJv|6zj=fOZmO>oQ6etc3 z#n_?Gz&pxUR;7bxN2mG3A3A)VZAyg=uZUi1Ak7 zZL}XPkMcg%7386mb+X=K4L+(#i4Uqt#aGH$zCIz%cV3mHKB$OyMT&UNa4q6BycQq- z`BS{Dlz4J9XMA-{6qYE=+>*3we=lwBLYV{WMQ!4VZ?@4Mu{9W0qQ@7ltfQXs zV&q@e;X&RD#O-%0pcY43jl>F5Cl^40y79|yN{~8~SVz|;3&9^H)C0^H_BxSO6R;A; zs~;KsudA|%vRb{T())XS8)lol6UzO!AR&!*to+>w<-y)ao#&185BaE#v4d>UCRFQf z5|1C!gzaO9Q}@XTZWuE`QKlg1esP(%EQ zIt<9t;U`Tc7`sc8$9dXg)SG&|F}A|ItZi^TRErxkXg}&R7ijEgCQayspq5IV=C3X2 zx62WuuXdr|0Ah=FO6a~=g)zU0&13Y7_0N_=*SBo8?o$%JmS?h|-T&Co1(aEk^hT1$ zccv--i1kTMg3R&9>{o0eX5Xhhv_EgL4%#(WGcOI#J6hPHtC>vgrW}`a-Dj^Jguu(5 z?oET$XB3)?P72G;2 z$8&T0(DIN-Jh6Si-pap*HR`n~ z@%d^=@YouGkF;l5h0eT>;}YQdK*aY;4a9!hQ@+HV^qv(d&~KIEURPB3P(wQJXDaYz zbSF6UM~kQEzcc7>(O>+(Z_&6xGD7H!i$e6Z8k{bqJKY*xK1)lJ2VWs}wChva3AurG zAQF?h9}{wDb~yRb5RDZh>27jOyyHea3dx)98oLJZpYyRMRhz4PYoHmp3aVpY2_0H@ zg%Fu3g7$B7yzB3RJ6nbc{t7EF;V0=XG*6#CwF9@)da>h2w!v|>4HPK@xaWWmlH8sD z*V%zg)_P1`vmGam^m&L%3y!!sA9e-PIq}3ZjSrYDuETaiM>s!j zfPYt(@bDvPG{r5**uNHet2(jAwh)7!QeNjR?V2RcjTG-@N;bdQ*>PFy=uzsF*JrWP z!?ar}g)#%v|FD&_y4gNaD$^N|jAzEg46aMSg#2J6P+r!Ke3JAV$>_NJjvcZtW;2Pc zWu}wOikA^LBvOGJ$$w*7D~VM}I-?@xB9a~ZK(4)o%})*{jVcDym%U;x8A&j8RpL3k zhnPpFOY4msP)GQmzVI z%_@x@-f+(|#(k}whJptr;0i@O4!}P z@9eb|0}BKt6cCiMyTu&4#xA706l_tk6$3=N8x*@+zx96q&Go)>IpcUZXYc1(>%Mc* zbcdR%Rf09{a~AyH>>J7>=A%tY%IVd3qT0V^ld^7Tf2$`Nqj7HNObpEGsl>-D!mWlqc=jAe#VhkX&A2R477iOUXTY;KtijZe$TGoGio_sqnznm!;3 z4ky+=$AM{ck?326^m&C?_{s_I>?W(44AK*W)IOo!Wh|P%<#+l_4ZN6@JxnL1{!^Yi z{0&tTzBys+S~_$i+_1N9I_#lB!oHuGcz$y}OgSU^X;}ov zoHFFC=G=Z)I@Z2vjAr_;L>v8vXiJvh@Kpt3P{%wGvp_=@Uwj~5Mx}`kS3ii~PYxrU zcMao~r|8;=q!%b#3>&K@Kl+}cJM^}AUOW^jA7XL!u&%VpOBe4)k&pfUim2|$?`Zch)U*pi z2w6GiPlE7@^ODf@p|EYtpNEMRqPMf2oWCuWo!J-^bB-OTc@!ru8uIr`7i~s35_vXF zw8;^&Ycpw@#xw1a91)$VA)ggDmNC6GvE{9v++h7unDJcMCPZC!S(3n?%^1XzfwwPN zT@EOUrZ-7TI{%j|OwVh|wafJ6j$eUr=8W6hM^}#KEGM5%dri)&ap`aam=d+m&oT}o^u7TKehu^RrbLcy( zQvHdii}px8D)iG-W$e*ES)Plx4yYSiiGy*zh|Tq*KXkD&c195f zJ#j#@^;03n@XVJs4NuGSv53si_19ccs&u#Nw%0-)>{g4)#x6+gxm(GvEkV#eW(&8g zslry!TM_V5* zmLN}?oq|=_xM@>Lr*;wQ8itDcWi{fvDu>URA5w)RPnT(oHqzG}L_Q zoHDs5hHPspCu~W;lpi(XGI-kY10N#t$HKa42`FmFkWfyav5svUIu#Y_DBt1EMn3Du(Dpce{v!|CS)izot<&Q)B#}+gO!H=RAGG! z5B%vqM>z{iE2UQ{a*R7k|D+MfoXYR$gpTqgcMfxl3t{`Ty*%&JQ8wWn$o~~*HJ|

#?Kb-9ctG>3dJ6er`H9lBE##gH? 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 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e3000_i2000/set.000/energy.npy b/examples/fparam/data/e3000_i2000/set.000/energy.npy new file mode 100644 index 0000000000000000000000000000000000000000..c7ec1b4b9ae5c85a94091df632e64f2d630f6e63 GIT binary patch literal 528 zcmbV|-%FEW9K|Pvgp?o=OC&bh-sgF~Ppd4tDoHBZof2k|l;*2@#}I3=Sr##q5E8rz z!W&T@^|403Xhcxp+U301MH*3H5xxBjox5``KId?5v>$6b(p3|<5V))p zUba7@_l9*#a*vLMbuyh9%D87wr!xufUmkY{v!0)4Q|>v>_hwgfb4+64T=>6L*VyY& zKB4)z8 znisae*L*r-7^t=6o+^e*md7>1&S!?1S;OX&ijE~A^iosFXqfrsw#f$6aEwoJqr=OcR84>(7I0e3!OLIl>h($ literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e3000_i2000/set.000/force.npy b/examples/fparam/data/e3000_i2000/set.000/force.npy new file mode 100644 index 0000000000000000000000000000000000000000..6c413865946abb2e340e7515d29b5c305cb91af2 GIT binary patch literal 64928 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 WPZbt!yMsHnOl332e3a=_=Kl}BGJquj literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e3000_i2000/set.000/fparam.npy b/examples/fparam/data/e3000_i2000/set.000/fparam.npy new file mode 100644 index 0000000000000000000000000000000000000000..8a9203a7ed1971fc966a2feb2bafb5e755acd9f9 GIT binary patch literal 528 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= gXCxM+0{I$-1_nBsItsN4WCO0FF)enZc<_V)09-fNBme*a literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e3000_i2000/set.001/box.npy b/examples/fparam/data/e3000_i2000/set.001/box.npy new file mode 100644 index 0000000000000000000000000000000000000000..23b62d305eddfe4762846027b04e009b5d90b479 GIT binary patch literal 3728 zcmeH_F$%&k07c{KDKZ&_4i#J!7Z*3h#lcC6O%ah+5^)hv;px1M4MKm1KRN$Q?Mwdh zc<=3gvpWpT!#rK-<0-g>b<1kzGV7`)+(S_xn^5}1*TpsZmdA5(^X>n5n&+9FPcr+m zf1csxGg_J6VgHQvCMMnM4U8}sjA3EqP#tzTuvQrB2JJ_>*BcmNE*Qf?4&?_mFc*wr ip?>5CH82;9VWEEH2Q@GkjA5aE*ou$ln&zynfuzu#6S@VX^~RG#>VdMq`L(a6@5gp0|i7nhEBT? z<9B`EKjC#PmrGD)!JPY?vp;*E)AOy(E$mb}tnaX8fV;<9w>1MM8w@b@96MmB!2r*d zYu2rCUE#8Fjl0MHK0edccdZBi_}XQzt33GM2aXy!a;U+m@uLR~H3%^H|N14laL|~_ zm&s{qu(je>oI=zVn6TSi37srCrEM}H_16A0YlAx$*M}=ACwk(cXg>P?+##55 zu7KCHY1r-2pSs2LQp{FwLV=|rozz`~U(z@D=wnQNsx>IST?*%+Z-lr^H^If^u0q$_ z2}5W5py%Zpg0s05(9$|GzUK5z0^rg<$pP%fcQw_dR; z@1x=SSBpCB{m3i>w8&wwIG2^e~TAT#QHZF)8i0C}JD> zN1)~WD<+wegbYa>2DP1q{C6_WUQEW=7drIRI24U8f!H?b3HxoOP1)UovGG#^TDnMS z5C59g)mqfrPlLwqjYL3m0XyKH%iiZoXiA45?6^_L^s~ie>AREo?O@EA4|YDGj-Z!BcVc>h~%BL z$UQ)t-hC(+c8VGV`%mVA^}`SZ z!iN1D@O7??Di+qjPsJJrgOe0x9ZYB(RiJ;-cwy-sC-@6xm_N-*xLaR?LpF7IVP#D9 zH*X8&^JO$?t35<{uBh50qvKXih}l#F-B4rN#H`TC$e4ciSq%SF7bx%hp!ShJ9P2jV zQU78*&vHV)L(bSxdU4~X`cf~1#`7rHP1Diru6!!PQpo$Wt&8fo%eJ_{{ ztHk|$1G>DVmm6)0%Lej4$HgDBecCjG}%`;W- zB_W3yx#*Ba&nULcp_zSo_MCZsjY5xr3Z|-e0mVBb@YLZI8?K>A-<@J1bGgfA2j;Lh zhqNf}z-_khY&_sCAzw);YZ|UavtvZG`d>3UzcQ5tnN_hHE@9YE7Kc~=p0Hu_l2AEW zlj60@*vaTi*n8m}J25X7BM!!6?vw;Xn1;!)Szto>KdSNTx}3J(H>NSaWb}CK zJg7Dp)0ath2tBd@>*E83vvsxD{KN}ur^)Gy%^$_Pr@MiSX7Xkn{faUJ@WkuT`M<%?6CSumA>Io3MWTTxj1ehD~uJLfdWe zWwS9I>uN$@)09|u!3_spZ3U5b6{dbK1Qm8xto5kH(&8}1Rf!+Q4Jtsco+PboqsH$Kl7B#>t7r)-E}D^td409(4of-Qraog#OJ&k_V@Z#R`enk+WW%b z;?xNdqb|WZT8m02@!l8{kEJ8Du&5#iD_Au0Md7&7nvQSlqS5oQ76r}W&%&r^SR5^5 zd4Aed`8W}>!7i@M{|sOG}nupL;Px#-4)H;RV?F$A}hP zvBB8@U;OFfin%WwaUj?mi|&;{#ljs2I(lHmNOz&9X%PfX3*1O|g`}|-UPE0lq^$y< zdsgG~G+%s=G9il##x!_BIm`~ZV7S#^Q=LKO(3@F_`6Qz~$KT^y#|sMA!~8W8l(;^0 z8Twh+LH@cLKZ5JvJ$wO9%&&p(i%>&n8Rlv`G30}GKXKkAjMi1(-|5_c4XBT3a zLj}_aZ)T6jH?!&E`8@O2#-Iz0Y{e-N4H&73R|`_v5DO&>cM3$>7d_bbjf6xs5&jxQ z%(JVA*orH-WL(QOcz$Lg-b00rYKYz*4U?z3)Kk5X%?#6^1^Rk4cHeEL<)aGo;^)ld zSvbm*V))+jifxWdhT(HC@*2RP}ERPGF-&-s8`rmW)pKaX=2=?d%7Q6(+Mr*l%-2PnE91{OiWFtM(nn zL>rN-l{HM$s^RxuM%nF+ICQEM(w!zW=3bOyMWqRypJ0or&dZ@0U`Xl{)A|U0nCQI(8$Yi{$v@Wc^^|~(T31M zmVNCdyD+Z<&ZWj zDjKs|b!lkBIo$J0g{kLvHnE7;MtBmoUW!9*cTM`>pMV~}#MJ4aF6F+BhuhU>?BB^e zw*G*KYSh9JTJearS!&bP-kPKw|A-wvqD{#|PqTpCX$VfxBFznP2vvrm#!`oBCTWu{ z-$ktg#5A%npAG)5LzDUU;QA&3F6+cJ&rXM09*W7qO_zpkSEC=}4QcAj6Q(`ljcEKC zM;Nu6P_$G=+h66w!QX`T*)KBvQfxw}jMlSjVH1LEa9^Kl%lJ49UQbh@lnr& zo=zJf+|;aw%QHEJsC&TKzW~RkL@6##s>E{DO3WMkRM2|RL0Es)h}OMzgXT9o*i4nt z`zBjFdQyp#gTsXfUKUs$n`fFjb{Ue|`RBhZL7!aedUTfN=sxdHzKPi zN^F|q0=w?Mun1_yw!>2~vu7=W>|5c_RzV|APKmc=wDUzR%KT=ddoCkQ3xjZE)glIaEJYVRnfxx(rw1vEMbJZ@&Um@V#Jms5{u_ zQVi|ah|+<@7@yjRtFA@pD>Sn4FLT(|H&0l)Qv&)u?0~OFRM7Xb4&_I-u!Zvvv99B5 znUiJ$y2WI(D7AQ)C)e=#>Gz{lHNT|=Yc+9*WgN2c0tV4^0j!%e1r*k35c`v1RyW8};T7s|<0MNT_DuR_bI zHTY=JMKCY)!Kr1%=zq&Va1h(0dD;y{XpJSTGo7*Xln1swb%A?|E6V#+!(!YD^qymd z*qGyrP2a2V(#8YW>xABXuDVDZ5U6ZIkItpgEt&-#b2%+u*+rqPQH87{&M-dIpMJSj z;ixhXJMv^yJi8bdxwg6+V2;4tTxfN7`G2+e#i|15-K){R%VHdlt43YtaN(!41U}Ll z7`Qs%z^Mw1$W>y>ta>aNQV*~CYJBl;WhGNp@O#a7HYz6?D?MMa^o5Nq;G%@;hW}!T z9Xep@`$86G8i(aaOPD6tr8#Sp;9c6v2E5m$XFFrDO0$AZysc!TcZg}TZw~8j7J`g) zUAlKAhaFaEQ)+V;s||Dur`+zA7Z#~@k|(Ve~(Y(zjTd~-6{ zx0@jdyc>-jv*Qr^k^h-=1vkYaQePX7Hisx2ywJi*e6(mma11(siopdP{yZEBL{zC3 zEuJQ&#k)daLMmACFovn~TAK7Z1_oEZu%6>}=+{vlQp_l1J&sDqU{D8`cMiwAqgr&Z zR~+_zj=-EaZ8{^v=y}qxke+qR`1g zh3R%0k1mQCD48jzvp1|Ts(Ulyht5&71+75a+yr5XiXYVNoMDr(0*hBJL*)3C=#icS z?@T-F-Qb7Y1#1;=X4m4J@mxqefkWZ1pxN01DT_-nFrW~|oz`Q?^BoG6%QD)rt`hK> zj#d6#b8+1vcCW?hGe*=`tp*oH8PF-w67-uhPof ztzt{3Qhe5ILgoTD^t}BQLn@1KVoo)N|0~6oO_lh;ezVaV^bvdP3Hy?mgi8ZB6S4Wj zGI(zr^iCDK=d0kvzI+zw8q4+mL$PGg0dtX%Yg9SAvzBWRH7ObWYhbh1*07Pgez8una1_kr{chMJ*3mB+nU4BYkoug} zZjMK(dMTT)yo#19DacO=!Mk3`7_d7Tr$2H|LqWKy8V38q0+z?0fq=3QklRHp{HsH! zcV2|iPAz(6t40TgMnm*AlX>sSX1kgs)T?J4&X`xQ-F9kpeX9;lS$Tswa~ zdI5{`_2~TeE6A~p#DWeI+GnOiX{VF%p;$^~K3(AuE1|B|9m!&R0;Dz?WXE@3g9078 zz}ZxdgAR>;CRUW>oDwi5R?r&jff^eb{k*|@)~9NO2c`&L)~^$qZX459?IpOer9>f{ zX#=w;SG?yOB>qVSb}jNl3fC5scyAe!TZm7deTA(79{9eh7Ta}9=+2RH40SJp^fD=I z&dW*p>x5!@Hw%ndz8r(+%c*_sGz3J|bCzaI=W=}Eb9s%hZL&LlcW{NWY8JZrF2(-e z?)Z~dj1_yAV1JAoM8^zifqFesPA$TOEC=ZDSt&|2$9Hif;;0Jyp3Z~+EE7sy(MNb6 z-h#|!j#xZKPUB|Q!oFWM+IF2Yeb%`i8V9BdIk)U#mR5w2m+sJMx5CgrMR?f4Yiolo zPW@L3Bj1UNvlk07a90I3uX048PCXo`7Q>>;(e*?H6oZR7&-lh>+-zfOs%zNHNAZw+ zQ^%AQ+3deM2@P}l!A=~Ia7Oo#^*EmZ`O44i*@h^r$_mH1a~167Mok(#^b(ewsba%b zn%IC1V%nspfxfpBu=%SFeR!VBdiB$#*ZZaP$p1g~H=u!y4a{V2wUKC85Q|0Y53%De zq43Ypqiaud*_ahc@T>dEj_l*iruYhe7F@y_-WLnz#^b*fZQ50R8T+S4!djWfUR89Y zPCh~C`y>L9uSH~?910DUPV~53mwdPI*H5TpmipyvYoacNcMrjhPZi8`Ag{@N+N6y8 z#a8wf(Z($3C-@3h&chA+boS`(N1FWcqXM+0i0>} z)~4@Q#B}2~XOSPYXi^lP-8T$K{KkYX+Za=Yqbo8;8B_80JA&Qv*T~@S<=D>i6oXYv zC^XU*&6A9&=HwD=dg*{eb+-izt13j;Ek^BYBXVD3MDGVw;L;)!`uBV(g0gFH?~Sc2GS zOH}8~!HLXu_$PM4e?@O#Vz3H7QWj%Yu8c~jG~jEj72Hp|U{^u~PXFhFU7`Qe*XZT#fsr3fDDqMfx^HX3-9e_pg=-DS)iYLX9Au6Mw@R>Mn>~DX zEX3E}T&FCl!RdMj1iUQ4hBGE~fpf`m6Y9{{&IMM5#kl2A595|Pj8Cq`-wD-N-aCi+ zYCK{6huvqBczt-ys$&(#w^_tWZFgi_pN zQNqJpMx+{P%y&8$v}~wWcvxG)ZY*bOgRBIHm~w0{w}I;_Ikl$B>GzgK3@HbR7QQX%i;0c5xWuxC{FjTL_Uh~IP05WX{E$`uHB=4E!9>1$s(RL;M`;!AE_FpeQ9b>o= zB>@XZe`19O6581s3$>|5Y{G*!mOWgTj6+(PRRPxw(4Yi=5&p@wXw`QSts*fV%zVkL z7rbSU?j%A*2m)QNVG^|{^tmabd#Qg|_K{GmwrgX7MoFl@ABPzQ3Fy1?0(PH`!IU~} zx_2!cN4v$rb!;{3KZ?JWLpX-diNt^m30=qvgOYQ~5OFuknH`S-BMKR1B(cpCct6y- z2z@@M2cD5qSgVvwn(Nr{z7jg}^C{coACJlZw5j{47(`|z!t5B=_O{xzyF46cp6bxR z!S9&sBXycKSBpmNPr||p+H~4ULM6dEl<`PPBM+-mhBxOV8%=0&{1D;wT6curKdJcl z!-Q6BYQeK|pq|KOG6PL%GLt^mgw@z5e9Ga+^6c_t+>7|Eg1% z&o6-PK^f^AI^%r45+yF*6&k6vu;U)sY&Umdu&TDui*xRAt!7B=<&Gm6y#@O)Pw39C z#L79F6cdZQaC=*SYCSOn^=}s-bMJC&<{q8J;T0&eCEp z>J~%x*USd);pJE*F~=j`+wD76<4K2noSv`-E4gR$?kZ;n>#7mxRD_N;oAK#m6{h{G zMwEU7ZoRC~wp?J*1FA@+G$XT{! z5GM2a-1;d3MWjt_cOv0CCKf^8ZnFm|dh}#?EUwLo!Gv%v(ij+w>67(nQEz=x$qa*W z=O#8)>k)g}sS}AV#_-+ZGxK~QBGG0Qia4!;j#gr7oBx*$?#MmEWnyydkO2LPD|jpD z(0|*sXoPb(<|{NQbcK@oByZsrlsLn92e? zkjQ9CqzN^I7h|X6RG~n&8rg$iL%3}~_go5brnw9qp2%rPkFJUbTTJM|PHWuD-GCU= z$%;s88+>@yiZ(w3db@l*R2pP7boO?r>deBM*NbuX<~)4pwG6dOs!+Jr3Gw;%NKA+q zMz5*F?)@v#XR{SiQd>!QHkckO2kEZ;?F%9byOZSRgSO2G}VRZarUbs^==J@ zHnd`L&QFDHWdSbu{Q5orD8nn)`E9KD_MlfYxet33sYU14%VlRbsMLKKF{goIig-m~Skx0vUMGA8*FjA@CHICwFG`G+LJp<0(ZFYS)Tf$`Akkiq`b zzl`ZE;jnItL8ta8C~n5$`&lvf36t^jbvpF3^Vyi@j zmjWZ|LVZkcUy;#zzxhbhlhd(GIrThIhToOZii^8be2Ak>NN=edwoIu(L;nCpXx9q7vaZLg>RXCe)u)6r zH;u_^k{2RZ*dqVeWnsf@5A?RLz+x+P#n;1gVer|6jCnnJ4Yb3&V`dnz-v+^3XQPTU z>baWsc$T~o!#L}dxYuBIh9e&HJyL${83rtH=0AV#4L_}gA@^ehz7Kv~!gW%66V`O$ zKH-+-f=6Hl%GWf)(R#eFjC(xQuRke<-r9=UYbwyuWd{@k9I=mc@Rd81u+(3G)yWRF$CQ?(wI|6Z_*p4V82?KO70E(pzLEv$z~ z$&Bx6)2zPD%eGIsaI?+yK-Ah)K8oGWW|AbN~y;Z2?ZV( z)AcvnbZMt9XP=?MHJ&k$=Nr=8rWMHIUb3jwnC>1eMZ`4|YQMwt0$aU>KczmHB{QM> zW36D)Y{UJDi^9TA#r$V!jvarwUpFpZVeVCljW10oXuC72H!C5^m(dxGT09n2qxSH3 zh4I7wwBk0O`4#K1#@rFhjODb+-V>^)I1j(T>woPs6zgmeoQ_z)Tj_}%yPV;&)eWdl%w$3}@=kD^b!`4*J5g z5kp%s-AYEA9P9AwVGXVZ_~GT*N^~4shMJ&Cw4AI)QRy3O&8uR2K7VHYXMbYdQljB@ zqm1VairA2;`t)Z(CKJ@&umNq)nBTutuGv1a6<0%0M$s_;7Rn^Cy0nUGlDi+iv2F*R zvTrJSv|;vNmeMa4rK9xd`;9kjYKoMK7HU(4L>-$KePSEBJYX)4Dd_rNH1r#@*pJ{K zC{RH*JLwm@oXYu3mMX=^CL<

fkY2VHh4J09xLrgucE1tS6XaC2UQQ>9obXXF`{VfEt_#Z+WY z$vEtcQP(>N|HvNM18uRo_Pe0rR|{6}i2pttQDV)}DSho~;8kis$JE^5e5)4XrWJy8 zRU@{@YS3eIB+r%co||hz8+JRQwZR&lyBU*C^*r29uRwv3(Ug}boKes?SqN@*MbJ)H z+zk%EuN*f#`M4B@Bg^n-?<`bboQ{z#2DJ855wsn*@f_F;#CcVt%N1wLX)njVXH}^C zVu#vg1{8d5ztFq066yDyaMts*LjOkz!pchV<(-Uf_HTewtf`{UxdpJw;B5MVEAIK* zVdmBf_>L&Sv?so}Y*39swlcam|2g!H%ka!*6P%`&Ayia=xhmDj_*;V`oEa7UENA)i z@33D(n%L>5AsCx^l}&p4o2_@0Qc8!1Y>0Og%P)Sz(vQUBQB4s${UruZPRF4@t&XWC z>QQ9wHF)#))2}|2P3$e9&-on?a5ok-n|lz!t<00}ZkY+ZHvVL?OPoJC|B@_gdC-|F~G*77hW@273hj_mWbWCBRC9Oa_pikPUVC{X zsxZf*0kYS#F+0VC>&He@ml!2p*SjLtMyVLQx(;oo)rdJcnax;RgPDhAbTY#V`Ev@m zkFyk$KDi8C84fR1;&ZH=-exHgyuT8a_IBv=z7SgSTD)IYgQ*qySowzc z#?zJTuSCg~cGZM<9^dsXzOY9skJ|18uv5NKj zsbuXwF$idkL;TL)tRXoZ3spN(OXD-vZ(At3Onu4bKa4=*)?_?b6pKFZxR0ZA375k} zH1=yKW{$rMt;aDek28~POA_H)6^FWgV$xcejH6$*$fv7>_B$m)wXlU1*fp{w&Rn+b z3_)gO0b4k;GtEiUplMu~`;c*eu)tCUvr9_&#gqlh*my1T$#?^1O>DLXPn~PTwO!^!=9* z40j7#<~iW_G42_=`Iv71UcukfO4tXE5>$gyg{=cMSYO-4xMMXRrF|v{DoQ6zSzCdu zY8l@vc`d#CJ89!hKm5sD4g+l`)Jqp30}EV*J6D9QGsWC)~^ii#j0k>sNc*wI8IsufgU5S}E-gav0s~S`Eyuu5 zeaP>T7h?9lMd*RGn6hXtjA~2KPp1?k+P$%kGjY>AV+z?*j$J4D*O};sdz~uaa{eWJ zH7odYsl*S%YJ^J#H{pX}biA8B>X{lj0xAIv=vNpA+mN4~W5wKdXO{}Vxr3OgouUbdC zcRrI1dhwQ7op{F{dM9A<;AH3=eZgLYgyZvgG0DsxG0SPu&^FCug--(^&WOdXkVpik zUxLA-VANjLquF!f(ZKm({=JuM@CQ}$;90K^JTD^?>C?jXLGYF9(#jpWblo-%`SUB7 zeV`ULpOVnU+|XwWnp zReIVb4hvsN>Fd1?Na0zVn|!|Cqclv=RHtnPQnLS{L-(q*=;^|av?xPHzqt=Ao9QCF z*YZNf*lmhM4lSlv8Rs_XMl`jA>y9^r6hrxJ%*-_)4=YDRU-Cfd@_saQM9xEsz}Z-;Z(c46?} zA_UN6gy&Ai*@-oHY3lp@?LYgP__3HXy{z@i|j7w#g${g+$wBv zGod!`W+eUmE|8lIYCkA>4$2a}{Oqyqbp@UueU0M}yz!5fVBK32l1KCWm$-nlpiNlR ztqR|Yl`u7}LpvI*Wz6%-XXBRsWqKQQ$XPF&8KqwXH@4|xTRYpn zk>^(j@cJ~?gzve#>}uK*c3UeLiGL$8%|OZiy-GpNJZ*};p2?yT6EG+5K5HEuhdb8M z2=ENTx>w0C$PY)Bla&5iN8($*cv$K_W!-tM=Ig3h?AMINrgSki+g?VLKhN!S?MQD& zgkybmBdfJ~!6L47qK-vzIP~Nl8~g&bkWsxqhgZFkwV@dsL0!J#gDh{AhN;;b;HLJG;tlibR?g-@y6ua?2m1K zS7MV1_w`P>Vd%sH4A>{5H`A9wR$xNG@1`Tq!2^9K&q8{|e5`6Q$87FVzYSZ1uKkz7 zs$MEo1@g@6S3ZA>-0{Au4A+i(U__TzoNBAV&TMxS$omPAhgBGTD8dKZ)d+qqr&nWm zuk53Q+xY{61J7hk|BO<}%zpIc8Ph*s*anX>mM+mDvqP0^ zcg$l}=UKzFHpRjJOabel8UgwAcuZScz~)TXp||HQBj;xwGoDw?wl(SS-BkoN%T(mP zV-%=l|*5vzb5tlUnbI7 zL?#_|sq3yJJbF>fa?h7DQM82a{*4EHe9iWc;vSK@Htp4HVy{J9zwld#ZhS}Rq3mdu{OwnUnBy#RmvFx{EvgEUS+lor&K3Pg1Y`(L8 zqI0FXx%cTxXg7P~$$bNQ{9~`;x3UB&lRF9L-c3cLq#m!m<rvXksDRN_NO74r9iN zFiWUlDW}9VRilm7FL}yNl;p8Z{m2S}xOuN4dujP6+wmBA$H4<>_Ob&DMz5>Ui zn$&P6l8N2Zu`F2)g9k*T7w=P#%;F)hiGcULB&6QvK6P~@rhEy)_U@7Fldn3zh2gb2 zJQ=?@7t{@l!R8VP_t7Dfi;9x$}PR?QP^dywNCI z9TR`EfMJ&*IiN=?&PQR9{S|!ttVO}4QtI+H97Ar1XmNc5)8Kb41DrJJeN-@dsPnnJ zPD;gNw5X^+LS`qmY2Yjs#e{=dimWx$g?GbNV$YC?%;>k&R5_y#CIfkfCNs=*a)S|h zk66raG6IB;RjZKKxEvE@hSY6gA*OS#J!zhdGNP;nKeZ|}jytF5U1$UK!R6Q~59fOM z6)ycQ#m@C2;e@Rr-RdBtV@=i=e9{G7znf6qEjKK@Q;x_71~g1=4)-^l?W|aV;z4#u z{bSE_ZNM7e6$oi=z|8j+7;fqTx?Ha4|2z*L_j}=3iZ5<9RAXB|D@@O#>!Out>Qap>8`V(%UW0$X z&soX&6tt*6X2~;}S^X0!h37T1LfcPlc)y=))7fC0IjDn+>tdmKED$~cA2^Q|lX6oy zf@P1`nT{1Kc(@+*nEITxJ`2H3&Ud{tU$cHM#dI=Mo%Z|PX7kUdGhOc|Y)euc9_Xgx z?T`k*jBn^YBO4)L~ScIj7A*E9w)azoyzxg4oVw3QT8r< z@);J3${1aW)#tOG@1z=MG-*1|RX8LW)5|0ysz?tM*6v=8rOqZadi80+IIRlpmc}%% z-H3E}&UN|U+34^{&hG;3F=x9!?z%rv(BcL>I&R@H%LQhS-R*R!$S1EjOl9&)qSj&>A*za@ri{g`zi=IvowkJNl{H#SoY3iIAv$F(!T8CxSh0ZLaYa@0+tRtb23)XlKozbJUxN7U zN^GC|3hJEmbZzE6{h^F(ufE1P=auks8zp{iwwHMf}EfPbrrC{0^PBRs1fm5HIYD z5x)HvJ7oKk9So>sdpOq%*ZIum%WK&B9%34}v!1!Xc*8bcDrfdnl98SFj%h_DK=0U9 z{G#*hUAsQ%)ka`V*EXgR&GRikb?D5u3a0FyfR0>mk2P1v*GuZO?20nN9)iae#7^3m>PY)FQWm=GllByCbTPIDbKjv6VA-7 zHLa*D#*h8{*79(F>Krd41J@0h*p=`4)joJ?vH-7Q+e|e(*P?y02U;(i(8Jy`>Qr2h zN%At`>{owW*#8Q7w)zUcj|F&9*oeY@n!>QuQ;Jh(|0(9AO@!8g<#4$rCv)#b`0%0x zPrPI_RqT(X2PUN3#R@~RJYZh51|3xeNKth@TD8hOEOCV`rh|#cKqvSb__s z^AS{9jhsj;p1&%F?2E z#AR@eR&9xE!}-h~ZH=%J8(8+`+GI}^7AJVYY()h|d6-ZK&OqP)D(CN!HPY_X;PtpF z%*v=jy;cKf{cq6Xpo+S1H5~VAIT!A-+?%PWoTyLtM<%oA@fWc#@fRD`8ioCBX?VRo z3=-31{0rw=f@fQH4dglQ;bBl6Qo!2Nw8-X2G!|xs;MYA(ib5180D4r+Z~sP*yo@I| z-!P}x7Ivr`e>QfHGyz|tb*WT09v&(Y_!y@} zju&|DA@mx*Ta(a@=`HMDmV}=6(x#M*Q0(OQtC#e2slyx{O8%}(Q&O}@nauCx^^NK9 zzuv;M#TLlgASYx!n$l0&gny&tWI4biTmr}-Tc&t5%$ zA1Qj9_9hj_w*he` z0oNJH4cN(V`?H>`!0`NH=$~`J+&xNc?VS(ZIezG1-AAF)S5B3UZ=kK%i@-x~6zkvD zV_;AXZcO_k#OjsdzlO6yew#JM@2-Z*ofSOmy#)6Rn=sC@7?xigc_yz0X}jgru%6%K zwN@bXpbfHCzQNIo8cb&mxbv|H_sa|LxGImOc~-IGj~=rhBYyp#r&=#KNcD*b6InGJ-hHjOjg_%QPbkL z^?WZ|=zE9R1#=C1LziTO)NxZy#QC)fBEH8$9?P@WQ{FM7=wQgWhFx&B8>I3`{97E! zjL*d&VnYz(tD~^+YzT@i6Y$ecn}{=SrDGIE7XM(sT6O62kvK?BheCLzN$KwsF=H&> z=Wgmy&Ub$QvPX=2S2Ea0V=dauy}w?{b~aSpk*;NSB>!iHYz5E!*Y z>@}nueh0N-?q}1s&}FC;5=?LLdD+G@O^51w3paGsg!lqu>Uo^7bUx2PpSDGZnza~O z&wb=C*|DzffsmJ;;Zi*Rq>d}9;5@iDSXn3adjeddY zvgfn#TE!DZImVRWU=6=x1@Q7{H(do^WNXVg%XG!adDaLD^}xxX@(| z#LrhCkLw*%pIXeza>4jlcF5ANfa+2km|SUu?SeO$duBaSxJT~UcFXht-|;NBS;IQq zSNOtNpUvfBcz-pZ+Oak0G)E~62w#kqpWnbkX@|tacF366fRU4n;ncntE;ozOWia=7 zmX@RA&T7oy_p2vb>UkY--dWU$3hp&G?Ww^SA0->SriFccn8wOihhyiJ8uqoT~sso(p|9!^bxN?t4;Uir>0T1ZKm6$stV57<`MDrS3x-wJns z%<{)1BRxSvqt2JI-%CWa{GbMn3;4{&jL&3!4PP=hWgztbkEFAV%5wXJ6fg%R_uK)9XbErqhIPPojwbq>T zw|WxbvNfL_ewzxRlK2ST1^+kSo>)~mNP-x#IG(~s;HrkQ&p@`tgw=F5BW_suO3 zc4_j%X*xXR*fMdF$P<;Bnv{#NN42^$w7!ktr(YN0FXfCryu2vBX(<+lr53>YCuNTu zj4_0l;$*)wLc{1f#Qm#6tL#R6&W`>xuY-PAOqgc1*$c?0+8Tu-I@v1g(hcO;EDe2P3DPIKBM=W_9$E-l`Ea256U zAqH`IdfZp75+AkJ7`&k$&wk=4{^a9~-o;8nhsXzIloy%d{L!HEX*t?vx*@iBv|y|{ zLs(8T>;AW#g|^L(SU#!*kDfaTp&{k?a=)1RfJp`>)H_t{)+1JcDal`DF>RJyzjjm`zPrRLyo+FzZI1;9P71D;%{bOW`FEKS z{NSEyh?6zA@ncgu%N4kp<%}+KH|Sp{e=Dm%wz@0K>xv*{y5x9q9SP z9LZ-h(Oeq+*G6KYR1{WO%R*UQ#OsVA(Dk~D`S^H$-v7bl^nIu2@mQd#&SCH`GH2Kl~W2=Ao+c18m7EtR-uv4{un z3xbK(b2dG=f!)p#@#Lx)xZn89nhce>MHBIF)?~8r?GpU7Od<28w|}~#h(8KUqI)SC zK2zm*&jJx&JR%qu8|ZHI`ObPCDR3iy>cy`GBYdU;U)V>HYkMm5XBvvUntZb}hzYWl zw6qY@U*f;c_V9bI%g+$srXTrbemDL#m_qZsoC;n3#@ZIofBOln))}Aleb5}?F2tl2 z!+e$_LigzL2Mc9{>LkkRO`&Wb`C_6ePdAOUlVjEOlxr))d-B$f_-{B@G^hN^y9_GW!R>h3nJB$gd#whnXY+dgLNy-AQIJQpz@9z?XTYFIblV-3z zie`hX2-5a?eC%*KlL1$S^?JStEiFT0sWC2%a|QcW4Qb*W+VB#ogIV$FDtRT zyaK9L*2vsjfmVZjyk1d_IakZk{J0EB>i5}vsW2v;afkIg8xGm*a^@2g%^U_R@#{D0 z*ck1vOjjnINmGq>wzQpPQ9Xb4cOow3m$2p@^5fHw`{fri^MoEYy!(Hw4^6lRMk3{) z91pnmk)0kd#}~&@e&ALHJ5$xd*2@&LS?2Lr{wEp>xBX!=zePZ=SCX%_`M~OQlTkx+ zpHb_RF(M-np4PGOY73;fa1^F(6!HFcH%YIIrk+X`7j6)%L@x?&G-I(dhPWd8Zjfh7 znva~Q#D5)2gthx0Hl1{#zLa@SyAXrR+rP4K>S4@NRrxvMNQ~~5=lgQLvL`dB4y~tr z{Se|*B*h@LMve!$s`Dv&7g5}yz%3PTQ+`o_do7XSUgWum6r{MXr3$~OFXEncGF)%3 z0!6 z8I0)c{{4^cgy|NTFmIo*{hKad6IP0U=lhG_&GUe=@kiVw9dG#SYS>MxgY+~#gAT(M z@%Lp-!a-$y@>&a!A8IF_MEz=aKpXXVf?!1%EdR|4f>n(pw3iaQA<7M9#Bb<(&l$gM z^YCouYWTW)pl9G^@r?zw_6Ama!$@UnLSBmmoRS3ko+@3im#fo-(5l ziwhPa%*avbbF&KVVf9EDqruZ1s}M=N9?P#z82+w?vVqIspWq7TyaLSWXn?Vs1Kt#r zVJy`dn_DX|vAZ6HjgBzy`b61^(m+bc8 zuWWaxJpZ^&j=XGg@T&U61o9C)=y=TDZ%cybbIOg(eZgidqRa;QQsQ-`(RL!AonAmW z%jfAV)19&+q(87tg>0dr93N(<$obcH)-~r1OBwi&ji`;seaR41=sadIY0-FgL6yHC z_R$0HSafE;X90tfafkfXUZqKJrJBs3Ujk}retfG>1Qx%I#EU&onKR837yJuD_lHZ! zJxN)S-&cv{roz9^lH~_%W8g>rUI)rMh?14~n00aZe(xRYPa0hDEaH!~WwK_PD{6=S zV3r0~uvk`&dyGzk(Y$E*@1Pj~>5R?fOH10O!nNY*_pF!Ys+Y;n>ly~<4+{L$14TY! znHrz}Rgp&}De^O(T9kPiBFu>&$v+QpMf;J_LewJ-9yvZ2%|Ch#Mv_ZSd_0TlBB2Kzvh#fnW4u+;%S%2ftfNenDp(b#sT4 z>q-mS=Du92y-g z{`Nvnyy$odj2tbICeb0D6j_VHKlMmy(cn%`3gAb2rjCLIe0oYS+}8r%zt}*(rW6NV z>!3vXwjf=HF3Rqxdsd_0ahh?`TuU~!9G6JHk>AyX)nSxRX{y1J1$k`Nkhko~{5)n! z{;0D{QrNwrlFC3o3earQVz>sRKr@OA21oo7zCb5Z}F`jA|3z{R(w!Ts*_!cG2Y#fx-!2N~ zNyLF@7x85>irlt|cqZh#Ig?A-k`OJv(mzZnTDk!PDA#dk*Fm9Br4E&o$q%Nf!$*?_ z@~UzPR64Z9zWeO(wqgT34=M|<${R3OV=F4!&xkV`W{aoNyLfcKeZ3Dc3%bQ z^}cA^Y>B_4%<=l7IUeei;L0jHSP%njLys;$(y1fXm?r;4x$K9@6#&f_Ye)l}e)=2O-b%c`SOD8uMJTL!%&MYu zn9Rg7R#_Sbmk}*&Y-|y$_m|;U63W>)<3cu&G_qexH__GH%_J5^W7LH-y!aBp&Kaok z1C*~!jQY+j1FM)}A7$Qj`ZJpxn}AX;d0u!*0&1V7xKo7`e}@7lYxkWM#=mBnmI-Lt z6M=IMIi&kt$HvXdeC^v`?3vPa@(P!;=E`(@aiZF9ei+7QMxxo6=Gli7NTZH~q9_U$ z{c_n%>QxKUC|C703R{{K_?zYk^xZ=G4l#DzW5OW5R>2mGt7D};BHm~ng;zftSfL~3 zv0hWXkX^(wyp(y3;b)e8BM?_ttMVWB6Tws>VERgtUw);`(@G<8DMXn^s?!{MX+Lg0 zT#6T73#UwxB0sfSmPaHi@%R2pJokbmx80B-cBt1C-&S?ezk0|CQktv9KWQ#9k#e8b zcZUgwp6K%EHu8&(aYL$qszGJe2FPbvLTj}iUn@_UykrNAc5xSf4^b5iZcuCEs%ueksj z`VKJvT7;ayMtlj>ht4uRE?TL>tL&<9Bx4=IWpsJY@3;6@QUm8SL74DgD@sZG^ra`>B8!#0u^;quj9_I7jxk|?v@dhRikHX=sma}n46S! z8kpX!LRKz$lUP8B7$#rNMAu?4n%>gSDC88>$l}2gFK`@bVtSXRzWiTsxUU@sJPqjhj3`OEmQ&)k|q@`mXvXT z+lMOrX(E5s4huX|Brk-VFH%g`AnvXuj8aXpd6X@t?k6A3B2NU?yJHIFS!IuZ#y>i* zI!3mXSE@lujWPP&FTynXvpm(^p!jhPn{Zg0x7s$KEYb=At&0u5Uhc%hpOi(OtHqrHqon~kd29+u`SgU1t}u(<;xiA(y4sY?G~io{gwx44z5_mr?eD_LZ@ zgyKS!1VVd=uk|7dHR&a6-%myU{lP7K?)|~^`aEa#Ms!CEzsG8v=&Y_%;U*&+n9l`y z{yC4B4zEg>&87l2YgRRTq)2=u@_2 zNski;1Yvk{7*?69@RcIEi&+$=Zp&b9`hB_5B zS1!{{CeH`^Ot?`mnQCvA}^=h%No+k)osalI8%wA)KTDfBb2#wi5BHsj)+|; z3%?#Y#K+FiKVZHVpjH^cfNF@34G9QJt` zlqbCwmregGOq%iuCiWwF>gX*f(y7E!-xFfpHRK0aP=>l!rvw+$Pu|OGaf`_UMCA9= zP`E58jda14J!O<#)aE}K9flc&wDfU&0qVooWrYr|;H zq>_*7M;CrH?wIgmuNj7T5ofs4g)|Kd`X*|yYf~wFmF;o+bQO8!bofbn!y^KS z^JTOH^UviWXI?Mf2nytLDz*`<{}#o?-gOlQGUywQLnJrIuhx`Ob`7 z2WC{VZ4VXsb)gscmwjQE*F0t!BP-Z(;Tlw4+@PFnUvw3R;ler*A29m~yFz@K=tdD~ zJP$Bx+ieuy4THnTP%JZ#MtZG0H%$qKBl!)NZfj$O{pC1K6fs{d3avD6P9lBg)HWI3 zx?Duwo_I*SNnpwmRV?}uTztQsogqfe&Qp$cx-LRS}B?dBs*FD>6Z?OP4z>H4#QVw#K$%9sXmkF)+yi zLYOYs@~%RTqZj=4kT>GF4nO{|2v1ilh|eChN6DoYjCkQJoD8kS_U9!qy{gMgj_dGU zJGF=!y#>F|c|*5HhhMgKK)_+jmuAH?%S0)hL|6}4D)N3VYo#- zj9NEifTTSRewi;wnigZiW%5C!dr+@biV(B4NITa7AMFOxqukN)Wxr6o`=9Vtj_QLO z-Z(@aw7E}f(7U7x--|8?PsY=XN%Eff)5fi6`dEX~OlRD(x_z*78f1=Tin|_9ICQ{r-;4Fr*w+V>n!-{xBc;K>V03%~cb> zvrU^xE14yMTgo(NSsj5`VlhqFb_3f=uHt`Onx$)^ur?%_@{^qm#JApF7lX*l;gHXl zq0D+LMn}r>JPT$1?@%JKl|Qj5{yA*IdwKqae2jW#4NNp%#I4Ckw0FU0c7LJ*_le42 z8{?uN_gR^nt`5O?(wd`35i{(eGVwdYV6#w}w?EBbLuo$V+bhNE>?2Y5llTxRO2izL z;=3p-Fvn4vpFTT+hd0Z!#%gUohw8C&-n!i5-wW|tR)Y%iFW8kdh`k+k`I66;^d8oU z`#IS|JH!dcDbtd!R}6!rR!AGE%d@GUiLoq%Z>W~=;_g}uepd#(Ber%#F_d=H<4~3^ z4^Wh1C8i^||J;>0sI(ehC!PyVi#Ooz<5CQ|^U>hXs}(rj94*AlY=-_26D+GSf!2zZ zXnbRa?S8G8f5w=!e?Q0!)a5}@70^wwfd0_sXl~1b`XSH%n*%uZ*AT!9;y$iVYEWdRPBuy4czj{Ow=3I$n7@cSGMo!u|l@6|C_ zK1QAI85qx&9gD!at~zGX9)&~X>$%+=i)%wta8WG+Hz>1y?s`1l(^)M1*TzojtMZe^ z@rXT~0JpiyJO%M6n5w{!xe#Z}ED)&y)vW9F7nYqT$0f-x_D{%SkMD{2mV+vMthp4P zO_byrf5|gBA_DWSQZ{iy3Ld%KMEP%d9^X%qPq7Qefq5$Ya@I>WAzYfDpgh2+k`TPw ztI9cPhexg{@h!LI`A(`6S5c0mu26&P)@kwb`ySYGR*T{bn5toeI3)|}#X^OcyERaSJv1#h3SDAtamaGYe~i}R3mzK6`2}%gPHAv6 zG{ER%DT>#R2XkU_5?3%q0@U>FL#&PF%;UFFM6ugPTz4t0733yWqmt9P)c_#1gyp zkO(Ah!@FuYl7C~NQ8RSUs*8{AtAxE_83yb6Lz4X1db11hh_VERgK`kzSb?E?E7*#Y z_nG;uHa6hDX!O5)oBi4y&PGp{=iBG~VB5Nj*q)HvY+!miL~(8GAo&m1zK@6Ft9(|I zDbLdi(~&iyjxF5sfn~-j^VFUF;TB3gw3IBDf7QYA8m0JcVyE|SX=M}7d}2xWDwwZ0 z1#**bV(z;#);1v$awnx|K3B)yR>mX1Hj}*@or>O(!B`}hfPPMam_fB4^Q1n7y!uK~ zl%1*;;l}}W9#1}PlXoe2->A&jknX*av=Z-V@&-9&m3iUm*GyKKJh4-Xn91R5#9mY4&cEsYlemd*bT@{0i1 znb(CCRpe<_G!d4*HpT{@+2R$XeH|FI0@#PDanEcrW@x+cj^l2?*XV#+Zh=I6i zt_|eL-!kyX7L2X2MbkeIcwaKZ9RJN&(pv)+M`!rY-idMD*}{I(tnH0fH}c)ZL<@}yMYzeat^E|p^Nm3+K>UW5hx z8Zo4;5l_B0v(G=?uuaXqY;bxAihlMolaYDs!5Ag3f2^A|O0}?WgG9KIei>q;Y_?~4 zG9m_%SEk_y8%=Mo!|EuEE^J~y*ZyF;l_|^n=`Nc*DFTC?`twhey(_9v;u*v)R47Yj zl})*9+~Yjvu`mMtL!&{$j8i7;ZLqVWv0}R3>cBiP#SK32*_h@m{(T^$Xc(@8mi3HIopbVOVvZpPoMzW8+~OaIOU zZT`Et2Fr5}i>KslA|`zS6#eM@Y87IKP)z<`^6BU4@o$yQy6&K;{EJtIh*cgP$@ zmrF48z;a;*^#M*%I^6b`7$cXiAic>I^Be4NKVm1I-6%zL6V(Q`8)5e)Lg@D&?V#xA zg7m-U_=$Qbhg#w81Y)T>*5eB0`1jq>;YEJL`am2>~?wR@+s8i{)$J=~;`H z#H>3UNLg3!GWhHyKkWD}_Ig19Q$O0pG$%(x#VL;!o~C)?Dp|fL<~F;a zS3Aj{n)i@JeNJOmuX0(EsOztWoB^ucI(A zwwo;wl`=Cc(iKmWr(#Ga3!{3DpOxnmv}>6DVB#_LZ(>QciFlbP&))<+z&`09RJF+Q z<~R|z_>q99L>V63+{&Jj4{X8=WiE;hMe-Lp$}otyQh*YlnJeNYI>ZXQV>)NMyB-g{ zt;a{1xS=shlQ-Jy^6&|bI8z&Bux1S9RftV2+hz=tZXI4AZHcK?tH>w6ScvVZ!;Y;U zaL}qWNbS_%{aorX-A!FQ-^>g5Qmc^kK$lCGmBQ^AF+94?iB;$6^4e|Fgg>=AaQ?$O zgzeH4#s_%Nc`b)r@^x{48S>N)c`rWewwN@(l{ipN`T3@;h~BygPuuD-D}yw+IAi!l zDhYv3)zHn_O8YEUVOnH8_N*{QFzGv|bnCG;c@1h@w0ZVJUB1=qf7ht<;jvP10fW*zZj%W#c!n>9NfU`t+!WrGUw_crYjpp4#eUp*d4-{g=f zbbhOrpvS8Wb0^RaA3BFKBJ*K*q@HFVCF}*UCWEF|u@N;fn0WFnOJxsPAF9_UZRlk4 z-;huL?_HKgK9|t`RV*hk86#<)ys+UldoisaANG{?r;Pi;_PwXvUmm@`awRPPbR5)A zQ^x#lA=3{eHa5*t22SaVl>Qwo^X_BzWPB_(PKbi{Wo4Ay3dG_Vd7eW4vuB-g$P1Ri zrvuUWLmB9e@gZ2Cb_)~52?(U$6T3bXM^;9ne&S~~m+G}&e{Ld%G@$ec3jANcSiCc! z`)iOiZ|otaaOWLXVRN5lZzP@0IvTt7RI!JoA71$<;%&ZlY`9o~$4;YJ4hx0oA$cLB zlW=(}F(G0Vi5nr}-qf?*Yn9_M#3nf~R)G(lD96*9V{mnoJn!R8Ia0a{r_d}X!$*Nn zrc6N0rjdfTqz0dJ#{~n1j}am#(!4M-7oU5&4JyX!a``MB{&e0BG%nWTUZK12F>435 zFDEZyRWY_rc7*?%!{T@I-Ndgb2mGsD!r)oF34V>PCQo0Y@IRhiN`cV zPlq4sW@ zv>t!Fg#5mwt*HIyj0uw}u+F&}yDv6j%1QEGB-P?ubq-6Z%49hTxop^#8|b$ym8tzL zV+Ex5A9>ltoVrrk<3VlA;{A2vEVVNa^5V-@MdJMjHH>T`M(QqN(Cw4Jz-c+`*K-x_ ze5Zg7AU$KFg(837`jM&rR^Xf%Ffqh%Jx&^#LiiKbMg8#Rm>9HNYh*qdY1l_hgg%~) zEOPA)ESHypPV7}UjEIJ8O(-1Z#-Xh0CQ{^;`KH(8$02{jclS(ojMy_XHRG_2W=+|8 zYP_gCnfR!b=ge2;qq+l;qxgwQtdhX0G0I$3h$2rwBP%~ab%c~W@7(&4NuH79b4jl> zvY{P6|A}}^d;+?cC(_?9!^2i8^7y0VZKDk8Ve>jBkghj@>cOn*kx--ik`tF{FXgPB z5*MfLK5>aR>T%bVBRStUNccFy4ujKl`OytJJUO?H-oO50D`L$p+C}|vxh)QBY!e3f zIzv@!GfcE?1+}Zx3tw_X`W!v3by`{o$}K^mkyhxAAE`3}2RE)N{)2 zEgsHYs2_JW^2VDpZm|C(!_I5i;%IjnqOE3#$3+@r?+{I1qGyQFM!vKY$PvE%Hsbzd zW2DThM?U#&?jNKbXY{^TPiV%eAZx72b0sEo0cBV0pe3ro!nNcRN^!##nm37t8eSG|FY-mrv~m=5EW?STE;#nH8o|q|(R*Mm z{scDTke$7tyq(xf#MUlNvcdq@3M|}|kA;(}F_`$b{X}$EhTUaDo`*8F={75U9gcgZ zm2Bj~c$PUsiFajHv!ypbv#^46W=njVfLk4GjYAyliW0HXzL-s?J7MXRL|m9t%zpX* zVvKa!d0RCQ6iJ#GWkotKe_$Fr}Ph47!KD;Rr&eNloQk= zUBt4Bt$lnQu0QC`c8Wj-eIL8e%JFh9b$)QxMVenK@CSzJ>;QS!{JmuOra82Ot-lm6 z9;Cu;DWAIPmkh5>R^TIg&xrTTq}kdQU2*WRjcA)rlz0nb!_hvnZZ|D{I#E_wt{^EK zbaB8+m)*j-E?4q|E`hSAim+h_`QpRvk-wJ)Vyycb#KiNTy2zf`8COG5E+HkR@|4O8{l@Rxx_NttA#pZc?h2dwmp=+oE z8k~F}-t7SO?+dZBr2##?4p?)|3(0lb{2zI<%+L5xKTG^gn&C)ob-?3Em8jaDhjVu< zar-cF7QZ)(Ln*T=A_hhAf{zAm`%5vTrH=SEMuI$XnpO};e!Qy-*tKTFuAmtZX*lB! zRwJ#b22n1il*P`+9Y-mld36KKE?1%Fr8E90koRFbpnS?1vmB=J=NeXwjz^!!5_X*KmrlRy zl&LFVtJ3>o%U2mbwUOAdUz5>PBFEi#wXylHDep@xYQ-_1*!u02?90TTOt&SJazT{G zum8=Kf2OlU44iHIp0iOAHz>dHi`8z8#$^2zw5o@|_#iQ>^J6jIT$Nu?j)cPEC>Z?w z#s*PcR!;rS%3-uSDw=#goi~ujq=?HZ$F<0d_;gJ*%UAizJ}D{kXFg#t^!myipUCrj z+vT~vT_fwPAx}m|HuJlZfIEe%Jjyf_9-D&TgM`!M@%IbXATOB-CI{{n3mp)Q4^-mSI@0y% zUD})XU~c{>%2hs@EZq4dK#%TChraHDbuf8QVpn76C0#BzNsC9UZ^4zokz8AC70ieq z)3A4fFn&xKy1K|mRkBamI&``)C+Mevu*M0VQ+K20uoh)S$!o7cd84!B{uWEU$UUH8sRD3q8K(XDKpf?ZAks&barf4xz!$*q%{_ zFg^0?Z(j-Nm3q9{gK~Wb$|0$+4)-=45cVn1eR!xEr<07RmTRWJOPeRpTZOP;C6GSu zgxDkA@W`!zlU5mKzH>mUT^;^xj5N5_ryO3IHE2y#_BvpoKZ$(&+;GaNYpQ8-&V;w zPQ@WMJRVJhf3X1SP4>N2;jc_G*u=iU=*lW)h5tn%VmjsHO{1XwHVRVXFT#!Z9b-BI z;ZhQaiDq}0Of@lxX?IWUqXd}H%up_b^3d00`M9ZyeDvv93@Uj`-&X~5E0O1w7XoRw zNIPp@sm9;uD)ViJWTCY~g@0`;VblCVvDZYFKmHMff$!-Y4yAs2g$mE#9fjylMJ^>N z33bYhtq76k|5{>U8==7CzQ}Uzx3XN4-h;t)vV5Y{Px1bfBx!$E7l)-e;ruOW@%%A* ze4aLW`7ewTCXv7X%RwDJEy|UcGYiCKZR?P6#tc))-=#OK3hEK|ls%FYpDg(*e)NfW z>(kN&+dp*QsS_Kfk1kISB%i}%+L7qANj$D+xM0<^U0C#=BdoHVk=A_OU`LxN`S!~3 zG**+VuW-Vk@fHTzYSyrQvJXNQu{i(HyB%bPfSh6+nYIRZSJ@)ss16S!zG5x?`p@x; zup+hqgG5g7m`T|ank$-bo`Wp%x^Ar_uh8`>M9F)i?mGF1IO3#KG# z@=d2aFmpg9_MbMVI>8{ix2T4WN)s)?&Q@pPiP@dQ6+ws zwqQ?3J1p-u02Qy<;P$(0z>r$j{g=3J58K(zQ(0Sr9x>wit;LR zAJRTan&*f=R>ys4y@M-T;eQcz$Pj2Iag)**CFJ^nWoL9zi4p@gD`_W zzq~M0PMa_FRbk)7HMn_Cn+L23HRv{)V6aY+`0TC3SoK-~FRi`gH^?-2+)n(2<(nZS z=<y}i)FM6JM|6gAWz8ox8xweFMR@uOHmuwuV*%x5&?$te-V0{%BYscY1loB(v-s(1MR;=8 z9f~V+aAjgX?mw=@lImRKOVlBwPY*j=TF&;reaj>=ss1_egtB{|nF0O37Fu%I-oj!Q z*7=2v8IS}mpIY`mZ*-qODNt>^$u1nC>_>4dCa?X%j@ebRv;iWn9@5VCT}nrBo;+Xh z=r0Q(j_P(_5$|@Q9k>f*Alvzlee(;4cvl3TET~~ic3pzYSn`-9{$|&IB;o3FsuAV~ z6YKmMmZpSa=>yV#2L_`64dkpu)XPucMUCYHWEF>s3G^X(Wdqs`wWQwEP}tPIjlwr4g{uTh2=n&st9X%hdqr_0+; zuYrBG8$^4wctiSD44O#0kQZGS&OWfk$XD82zSjr)=a>-V+5^Q8w?loh4ebc6MDXji zNTR#)@w6f0GYiOzTIGQEt**E^t`;XM?eJ5-4j-SD!(Fr%bC>Aw39ohe>TB7=8+3(q zk1qElEv~(_7$1%|iwDuJiU;Wl;zKP~*#DArAx#?$B@NwORF5H(iA{Xa92y&%(ELo3 zyJ%Km^x1d#IKml0FFvB|LKOy6-rjJ2F{BSSAYrZ|?QOcvj5;Lon%>-+oiaFn`Ysc1 zl;`QkbJ!fcH>~vS3zlr12%$Bbtr!-CmW5F;(tFH~y;b7P^z$PkvZ==@Vn3-?`qf^~ z91?C}bB-Kea=D959xczct;tt^yns!M`M{ovf3suMr{twXfadwk?MFClMl17O>22)U z!eC6Q=w?A5!{C<@N&Y%w8s-WI^dfe*It@{FP_AItrPKz3G)2X zZWTW5ha?|;FI?>NbfSUuKrP;9iap+5)#dN4zKI{y6+rrv4!^X5_H)nE9rG96B-*$W~Z^YlP z_XOA4!-86-9__Dk$6_@zoV$0+U`o0zR*;?&@!tsprPmuVYHgRe`o0PIVu|@MW-Zbq zsV7diz{MxDE0S2WT^3ddGSuew?JcmpV+|z@CsdYHA>+Io&IMIr31vJh>`Wnj(qFho z95Vm(B8*KSj&i;h4|`FI+R3@-W1+({RBCZ{JMAC3O%lUS@?wmlTvWUpHVh-C=f7&G z#rYs)X8~@MX!GNJ>u`Qa16Kc{dFFZIdqtPv^|Wf(J|zyAY#AYqffb~!2RO>v7Pkh1-(+|r!O=!uk}(;J^zDA6-6V5SW3~v4?E=$ z4-IDp9^+ljwrNwAcvK|2Q5}QDlTuOtG>kaD32>lVGPqok>*vPfTxKYu&p%aX=UxA3euFw+Q6_Lbt*3P5z#XB@Hkrn#g3QkuYD%V zkSbvpXl6Hxm`jf=-!U=OhnE)0s zqH*C(GSf{hWs|MtxV~vU^B*0HeU#Cg;@{1bXDf0cP=(tCRkD>%lutSGnf;TZI%`NA z5+AiOW6Eo;YLp`{RyGSdc>|wLHL*ut(G=ZCz`8!sSUoivMG80Ja;-n@x}#ZT2{E%f zt62Ff+7UqKVy{CYWU^>a?GNIO{1x%#LGt|3t5C?w^{_6VG^Y1kna`dWfx^VQ?7%8T zK4g$0-?L5%M)zd6igGja_YQ`8ojldr!L&#II(+_6UqxA%j{m|ifIPI3Pk*te)MKCU z`TzU%>L>$SH%*prcuv`zQ3||>n2)CewRv927-3k}NbWa;{44=p;tLI%g_7_Zn9#11 zE%i@@&rdAG_VVTs#oQ5}5}DzTgavsQ9taw_wfOhe8v&F(RXwlEt8z>5vOmoa*KWmu z&@$5G{+A)H#WNT3A`-jK_kku49;C^)`*|U9zddSK(OL1_jxD6Qc#wbkMW7ibTN4+3 z#|GLVwFZ-jVRFLU3zE#7yf&rSan=T%q$7_!7$OYPZ@{}-wpgdU2D7eKV91cwRPR(^ z`r$gvqk2+pM2C2n66rss*;q84&NBHP&(p6p+faXg*5rjTp745@8Rm`0kN z%t8-HD>mX6W$l0aT4QJbRy03)BlyzYnRbC3g^BAhq_v!QiB<5WJl0XwMmPs`fNe}- z)l1&8IWroq>}L}5P$d#)dv4)hs}f%~CjrMgrMTLbzI?=jDDo}#GU9I#uS|~L?g;}eZetsI zRk`pw*ohz}9Vb5XS>|D@4vFoX9OtM~IJepg@NsQWeH$*xN5 zdrBTduEVV+Yw!~>ZYWoMA!iw8?n#&;O)UmE{P*wN>)AbWeR(5h*UEvsFKXFN!#$zP7U?qXWZD^!9w&X5HoKdKU_pS^fmiiRUvLq zevs#o&hB?>MPilHef1-ojh&s(5+}>^n$oD_J(zZAB1^>tKkmE$X-V&O755)x6W+=_DjLnlO| zo$^~&bCnR8q{a)#J>^8F!^( zge{+Rd7M%sG>FYHc#{s7IQ>YR3`f)yYx5m%7Qigq5|YDp`Jmw+VfAJ?o#LGmWV zEw;pbw*oW`@`S|qN}NruM7NR~Y=7$TC$7uIh4uBAGRl-PFfzh^!&)RzMs$v*x>s@N5qY5qaBtexK^@^c3oG)%ad{pe$Kf1qz+GZzZWc)eZpjyLVP=6jt-iI z?jdhk*VsbjrjhUW81aogE7<#n4{V}&J5#Gmhmm|OtIX^R4YvO3WwohOS6Mr$QQ}UF%d&xwx*D;=S27$d~GPI-l z8|@xF8`8;Me0{*4D3`N$$8Nx?ARNhf$ejNsU@vj^^9~O{h*um=9J|N9p9z2z&3=T4 zXq*p?q`gS7@Xe%}j3uIc!fg!RnMXcWbzW?K70J_RUoGWQ-l)WrZ$*)xrE~b|Vk!dk zirFw-($32id6z^wF?MO6E^(o*w99a}@^1E@mx%Lw&)69GB&hpK@=mW1%AsF}j_hQ3v*gR7qD5Eq&JPp^XY ztyR3R*OGRbHB*0*P>JkPO};YVy}|w=kHrhE9ib_^MY!Z*4TZ_J2$^nfFm-Mb`R&c& zU_^Hfquk%HPRJQioq5p%?IFc9e?KjVsc#4)g)aVfZ<}>+gZ#oLApRI-T3)+jkd;xiTY|s&G3@`E?ETUb0&uUlTKGm2{ z*OTAi(l#uc?Sc{WtKc}$4l?`cE+o#FNAG4#{~jjf?+y`vAuaEmvK4AuCBz#_%J7V` zed#8vgeLMkT%5L9_^r7CuRF?d!gM9pU3Y`;hKbmUcG?IhYS z8n+(WWpz+bC6B~E+J$CEx=KL-Oo$O(p!ygIS8A!R^*KvLE59UJ|)6Zry{Q9E~mVAl)2>1J9 zU>Wr>)ydGIU1dwjm$i2v%{6JSqV4kl+$TTozou?hPG0bzNx|6mJqqfS6{SQsX3)GP zZ-hL*NS=(~i*MM|tiIU$Nr5jR|6$95A8gM=Mc$UG#GfdV_gqls&rO?|(KYh0wX5-I zcHt-)O!Ij~ssjy3%dRKC8P$JR>O0xj5~?v1l(=hb3g)U3BgaFU52H8x#3{-KE|ulN zg^_$0?X$5OIZ0?fLc9=}q5RG_9sZg)2=}6n3c>>|zTqhO^#;%`?yr4>Av4U-+GG#6 zr;CMszstxcV-A~pdOYU}Wt$V5F(r|_h*f(aBd$ak%^OqgDbIF=_84#0;s2YDS05fM zPI9-$zb)RxPbe3o-)bEFQUPW$Lr{K5yJjyY2<9;xNnf*oP5e@DHyiqvjWDQ*X2-N+ zIeon`OhcCle?C`H=4AtPFS#RhdkOkXql|lAGk)#O$3ZoB?9j0jIL)!;TFSA$g;=jQ zbj82^RbU$NH0Qo)6uJzm@l~I^*8Nt(rcVQ+W$m#4ttEor(r#SK8ro^*NcC_7{1)kQ znM9n0kH(6Dm?S1yGUiTaO4l|IeeqTKOEBGXH&=v+Cuyp;u$P`Li<$~(|ja(o_PFS z7c5b7hig=tzEt0GMBBPy)C^s|tD{8hU0sRT0fz;NE#xWuS%mYI+I&lKDYh)m#~a$A zyJFLNVXD>?p;g-sjOsp7&0vH7N77kGRh_+E+@RYWTS~=u4m_C@5`cY8GTXX6GYC1A`$VmS{OLSVuZ(g@$NtXmi3CpcKssp zE{R$DHbEHekR!~_y%F8`8IGJ)DiU>k@vEz*Y`eWmjARyWvnRbbRk373WQbdy<)UG7 zD2}%X!|(B5Me!l>6kCwDQ>pU06ty3+X}bN~F)%-lduXlf*n`>>no1@CMROr)XfbX5OKZ`Q!I%J+}8 z(C~Lvy3?;+_0LFU%C70S$ByP2dRVU+%|y=#3)wrM5Rd&l@F;>j9CBrg9hu#5^ib&p zIl{BD8gKL!<$Y)N;^pNamFxKt&+b-UrgBEZmC&Wn%g0L|0UQ{zv(BCL1r4t;oT3!-h3^csO`2 z{7=^6(lYwPhPYw-$N|c7mqub+^K6(-T!L|TETrN40z6a|AwPGoQkay79vi|{^^RT` z&N+4cy1AH9?t~g$&cyjfYrJYY+Siw25`7y778fJThSPn9nveI$m zQx0l>r3kyN0V3yQjj)xRePq;&xqf}BXw2NwMK4WR`Lsw(n5a$< zWQqvr9>rdmAdJ%dAvShCiyv<_ur_r`${Pd z$wOV~@!xA91M1IPJ~rA+zrtu7-kGk<{xFYT)!8_kWG=s1V zQ5(@hmCSu>;G8#T(Un~)S4$DXSxK-1bsDa5%TCnEKF>t`2q(;};CEo#L@b?|hm|ue z>E+u=mxZ`=G6@L?_gL1@_ zMmn+)|Jk%ozl0jk-wCbtWL-f6^zErf=A|Y%2T{kdk{Y11&0 zzF4)Z^w9TV)?;`lrMEvb0?ps5Iy@Q&FMZ}l*;(`SNmn*f<+* z|8O7MM6Y9^1O64)Wz=Bw`;`mNCjl^Ttt+>^4u=-~-+2{=(khz# z+Q=}>p^sgk#PQzg zmRUOvDOtU9}vOLMT4{D=Lqrczk&%l~pNwSg)xP7FolVchi)AH^AMTJ+{By(Cobp`Rl8BK336F!>;6o3la9BOo>lp zzNB&zUKv`WZdCzVJg`AO=X?y?{|;eE?DRfbuQD}lEB~-FRIh3_8gWkkgY!V+X}LJD z-9iT4%)^#toJ(m=hWeW+&&qPcGu@s&FH?1_ftdi*D_ni&K9!;E)^)w}nc*jipyKWkNq6Yzrqp?u?FH zV;l-U$3oHX*JrVueD0I^{#e8;!F2MTrW`Yn`~Q6`wo=16kos5*z2S|_?z*z-=NYJM zFJO4IhEzs2mIu~{ASy~nR+Z7mNKN`mqh@l-v_Q1GWFSk)q0{+8=KFVgigq=VW|syi zHq7Mhp4dsY**68pn{HJN&i$a;w})@br|hC7<7?kJ3z@RT6IL#*6rF39?1trfxbL70 z>0X96n;ejQ$XpI)Z*Jc&Rj``UQMwg7AoxAKT#ZeY)*bTDcUw6suAWp*HMCd8Cs@dv zVY4xE`v%m}v)F322eis6FqSiOf3h@f$rbTaEyIK9R%q`q69pF?$qIA8BKle$eH{*y z)+4c|aq_U%*K^6VT!RTW*bRBE49jxpxA~n9wO&z^n#=9&$?r(F^K6V!BW|X4r@oCF_ ztqRa{H9G}f%z%Dk7G4yUqiJplwq8kvQ9ggqstR%F$1@REsYZ^cKQ>!^773*fMXS~1 z{vMALiOwm)taAqaS{G2dTo>K>j$BJUrS2i6l%=HtWABQg(4#^4C!-E&`YN!8+7o2hx<7)Q{u$@dCZ2CM`2@e6sD7rw^BbE zJBKkNN_I%(z(|BlN*7z!771g%Z3CuzqnU1{FxGA=e{V35_eW|$rEVm{2NsIh`ary7 z_QYd`4_58;!Q8I~vQ-Oq^ibn8r-#gDNCUj0hL?0eU5=g?fhcz3Zl>q3{|!y)M=o=G zQ|6#>tMblvB{Q2AQk~JIrsKxqec?PjIco#QwHr`8 zJr4u8CU0$8I~NJjY7#jwy!3#tV;>| zreRM`L<33``5r+qVFE(_ae%zie@M`$ys3I)dKtbde(q zgmsm={P{Fr1TV-C&9z<%^?_l`W90~+c7f1}W50!y4g$&TUHc&ndmEBFck{XUK<~BB z=om4Oo`aPt`mfrSix@JBHreaQ^1PSgS8<@&7?C41&Dk}ZN5<;Jd>uJtb|6}Ae=8zSXv?2PTGArofv9Y1D7&f( z#9jJ%HmGUHgO=CvsDSI0T`CjL@ZG)S5(3#TpyBgPoG8+eHfIgw6ORCTezoN4L%MR( zJU!_}zhjdIoY$|R_7-g-clP7`&2tvk^-EKheSN8zx5&br#(Pvly4frL{;^YDHg?8p z7jwmK%sjkaF$ccVTz;n3c6Iksyf?F8X8)iP7?y!H^dkO`r};K614~2qC{5C-Q(v#c zn7O7B<`(jH;(TRO;24YuS&EJQ&1B1Q?r5-~5*_DivY(FZOE)q*m@YFfyK^nUlA*AF^A%%s|*LWH{1;As<{%e|{{w$@BZ|26@CYYP!wJQuB2O~H-B>D1}+ zF#4G_dQHj0&qh|tvhD@Qams*lbRibB%15GJ78-Xh!ri$QsIIGKm+1|mx#qh_zgsFo zsqg0XsStj#>i87%ISEGA@LVHYnwHWLD&U+gEwbDi3KBeQl{ zwn(H8sO_s#@p+X$VvBX;KLhJgyGFIEji&%t=OflBctLCWdL=g zvzGMCP9aaltcC2&4E>t!I&$~eY}K~HVagf9-IfD8kvI5G%d&fgvAoiR`c~g=!v^Q> zQ7(9!$Wd?XV8nB7|MneFXTQnj{U$Q4CVuk<-8H;D0R$Mb-C>&jg%GKKf#AS|G+*U_L@6q03r<2ZXM8od;KAKwuF%qb9K z9`oJ6cqs?VQ`NEl*LGFKPQ(p=cy%!%I(7SIIk4|SE z2%AzhnLk2Z=C&;qS5NE7@;)EM%|VeE(NsrH9T|+fW7wm5M@_cApeAR`@kS~+4;o|q zMWd^H`(M_PAKe4+eW!oT{J_(Zt9-&%z(>y_y#1m3A-F@iS>2nmdyPHR=t{JbI3Rv1!WQ`Bk{T+Ds1K z;)0BMImk(I;yYwIGRXwn=lU2%PIhRh&bQh@59K}QqCdx1VBj?V{gmaR%i9bLDlEhM z=bZ7dC(Y)*8kD4B@uIb#aJdvjJ-txu@_H*)ZqSfB*4!5dMrMgX7j`TEiRa$j7*P{K zv1?%hmZ#qmE~}f$5*PM4J$xq`jZPMhA2sBzT~CC+(-qkI(7%|gi%GR)c0fz2-n|t+ zmA9gM|5B0c6oj$!0`aVSp4gJ(i#x3~-0&S4GX}!8~zBkjYj0daOk~Y zKQ}bw&VVz_cZ4G6`Frs&Pe3nTTs_fPb{wxSy}D^j%K$@}*jiIw$g+^99+s=#ujA*I zYRB_^v9d{$&CrQ@RjW*u=euS~pJWsAU?;=+4?8ZV?0~(=WQ<~e{cBC?Ak0kt({#5| zlwu**k=g3{D_YTWSPHeq8CbFMs7hm0DQ;$#<9Ig<`K9v`%LNxTlW*WNU2&COhkRcbSiQ?eM+0Z1w&yd!qv zn@nSCA+*Zrf4x(NF$1blnN$MHe%j2)si7k6j_9;75X&#$5lt#;g!6Q5Ic%U7_RnvG z%DC@h&?$c$G<_m!!=vD$8-)dzK8c{KT5{n$f2?cDyZrmF;%K@qGrG^jhCp(pquKos z$X-wSwXXZ<%2U?m;#o$bnB2NX7(2uwgLA*bMhW8GkpLueKQsB0B943rr#JVG_`_c1 z=)>XY^fMOA*(-UuQ6yBdAv-dRK~D;#o@#d2Exqd{v(#ayUoaJoAfqa@!C4__N!9P~2hX zXmYWdoR-&Ere5}=CS@oiJI9DH=2~7dbI{t>AG_kO_FOhr^Z6mq(pspN}5O3K> zj8;B)Cn)=KXTiV3OwLj_m(}0-d0qEcre2Lug*`Hs0XwH7_h~1Ui^UA2Pxe4?L9~En6HIjr}{zF+Sc{R`;EYq)B@f+l6xw=sXMC!X_Yci!(m9pN}NdOt==z z#JdKbn9bZv(9<$FaW5XZoxK$+vM_JvIP$Y9nHed=(!Enq-OWrE=M<_YzN^9LBX+Q( ze`A_MDVa5;I4KiUzvw4&`jwzO%W=cb+)T7yHV^F|+tbgVfd?+7Xx-2W^NQ&a3$~Ew zsTaEO=i6>h81beGy@D!HzBM1c%!~1mjI#qLe~Yybv~X^Dx-cXodTA?7Sa{tPFAVhL zHSY62%Lsz5==V7`sXZm;X zc~}f$TIk8wTA89feNjR5B7WYHB&Lok74@2lV#j4>ld8kWtvw{3So`83{Rywv-52M! zQB%HGFKoHqJ~0qxbGc}dWky`T4 zh(Lq{H<9%pIiKDg1^=JrqHlvQ!i?u=MQRWl8I*~D4Vv;2^D?2Y{)+O8?C#lHAT0ZZ zV(fWcIjbYP^_YWt%K4)f-&Q*VVh~cNE6*Q~5O+A^iGRoYZ}~xet%Y3{UH~|cdGEs#u~#oyWZebhy*kCW&JG1iZ2;x{q%uQTk=FKpcboo5`iJJ z-U*MdRl<2W--UXOG0C_?b9klTTbs{C@(#wf7|7j&>m1L#u$!Hw<;+@!7dP8inYbtw;iooTs0pg3Vj%;0|BRyJaV5+UU zj9-&4Hct(Ly}!0BZc6X%34d77tC6rlS1$Y%fQLgHOHG&eq7VJp=TGX%CYJ-Tn*F=M z)QYa|B!83L7hi4~%DAJ{MXs30>+2d=b{@9~>dZo2-P=SQSXhikeK^12xxK>vi{-uV z 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 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e3000_i2000/set.001/energy.npy b/examples/fparam/data/e3000_i2000/set.001/energy.npy new file mode 100644 index 0000000000000000000000000000000000000000..771741ecd6c3d362e4f6d76d29f29a8b3f7b95ce GIT binary patch literal 528 zcmbV|-%FEW9L4QI5|b?GqP&QOKF{-g-mTiKuwoZMtGi;761$KXx>+t#-}M9D5ws)= zpD;>YDth0c(I_~{)NunIdIP5oC`Z!cf=D5Dh^lV zOh+o)?wZX36HTu-p@2zuy9eFmfj!-BN9v!xJ-I)d^7ZWAWKYWH5($SxHWWA-_-|Ba zUKvJ>L)UAE6A1@zUU=ha?i4LEr3Sp3AOk~|TlGRAujyQ(X^mUjE-6<26!J04_*PAC zyW!(q;X};f_){Ue+7NtV_;FvTiU{5#%f0W0U;UQ27IF)P2Xn&NA;Y&`OMjDLX^Ua5 z({Qy?v&mHy(?WHv@FxjIboHv%qaYge(A#=#EyHC-y$a3+N;>@(AZi~aG#y; zgvb@k{7qron8Su$4pX!B48J!Np9!PWimB6vu?fvcOfZv*#r~GtUwq%7Vns<)@c*bu g2+bpgujK~xb>T=@k)O3Rt_pJQvL-VutSKx00_IBHbpQYW literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e3000_i2000/set.001/force.npy b/examples/fparam/data/e3000_i2000/set.001/force.npy new file mode 100644 index 0000000000000000000000000000000000000000..8001ccdbbfce7c5258289f67534375c2d6541ffc GIT binary patch literal 64928 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


-T-gqyG}Po@paF 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 U^lo7{Z9K#YFYHRe+^vxNA47H;(*OVf literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e3000_i2000/set.001/fparam.npy b/examples/fparam/data/e3000_i2000/set.001/fparam.npy new file mode 100644 index 0000000000000000000000000000000000000000..8a9203a7ed1971fc966a2feb2bafb5e755acd9f9 GIT binary patch literal 528 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= gXCxM+0{I$-1_nBsItsN4WCO0FF)enZc<_V)09-fNBme*a literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e3000_i2000/set.002/box.npy b/examples/fparam/data/e3000_i2000/set.002/box.npy new file mode 100644 index 0000000000000000000000000000000000000000..23b62d305eddfe4762846027b04e009b5d90b479 GIT binary patch literal 3728 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 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e3000_i2000/set.002/energy.npy b/examples/fparam/data/e3000_i2000/set.002/energy.npy new file mode 100644 index 0000000000000000000000000000000000000000..d4632c5c3397763ddb3301f4f6121011d2ac01fa GIT binary patch literal 528 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 z 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 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e3000_i2000/set.002/fparam.npy b/examples/fparam/data/e3000_i2000/set.002/fparam.npy new file mode 100644 index 0000000000000000000000000000000000000000..8a9203a7ed1971fc966a2feb2bafb5e755acd9f9 GIT binary patch literal 528 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= gXCxM+0{I$-1_nBsItsN4WCO0FF)enZc<_V)09-fNBme*a literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e3000_i2000/type.raw b/examples/fparam/data/e3000_i2000/type.raw new file mode 100644 index 0000000000..1ba41c4cdb --- /dev/null +++ b/examples/fparam/data/e3000_i2000/type.raw @@ -0,0 +1,54 @@ +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/examples/fparam/data/e8000_i2000/set.000/box.npy b/examples/fparam/data/e8000_i2000/set.000/box.npy new file mode 100644 index 0000000000000000000000000000000000000000..23b62d305eddfe4762846027b04e009b5d90b479 GIT binary patch literal 3728 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ïkUm9(9-53+%ZYVuXq;3&vuY! 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 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e8000_i2000/set.000/fparam.npy b/examples/fparam/data/e8000_i2000/set.000/fparam.npy new file mode 100644 index 0000000000000000000000000000000000000000..88b5d9b8d64050ab487a68e0647d713d100b1b2b GIT binary patch literal 528 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= gXCxM+0{I$-1_nBsItsN4WCN~^)du#Xc<_V)0A2;W1ONa4 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e8000_i2000/set.001/box.npy b/examples/fparam/data/e8000_i2000/set.001/box.npy new file mode 100644 index 0000000000000000000000000000000000000000..23b62d305eddfe4762846027b04e009b5d90b479 GIT binary patch literal 3728 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

4laxJb7N8xA( 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 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e8000_i2000/set.002/energy.npy b/examples/fparam/data/e8000_i2000/set.002/energy.npy new file mode 100644 index 0000000000000000000000000000000000000000..929966af650ea69f26a00b79823e8c1efc478aa9 GIT binary patch literal 528 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 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e8000_i2000/set.002/fparam.npy b/examples/fparam/data/e8000_i2000/set.002/fparam.npy new file mode 100644 index 0000000000000000000000000000000000000000..88b5d9b8d64050ab487a68e0647d713d100b1b2b GIT binary patch literal 528 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= gXCxM+0{I$-1_nBsItsN4WCN~^)du#Xc<_V)0A2;W1ONa4 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e8000_i2000/type.raw b/examples/fparam/data/e8000_i2000/type.raw new file mode 100644 index 0000000000..1ba41c4cdb --- /dev/null +++ b/examples/fparam/data/e8000_i2000/type.raw @@ -0,0 +1,54 @@ +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/examples/fparam/lmp/.gitignore b/examples/fparam/lmp/.gitignore new file mode 100644 index 0000000000..7cbc28b8a1 --- /dev/null +++ b/examples/fparam/lmp/.gitignore @@ -0,0 +1,3 @@ +frozen_model.pb +log.lammps +*dump* diff --git a/examples/fparam/lmp/conf.lmp b/examples/fparam/lmp/conf.lmp new file mode 100644 index 0000000000..f7292ef96d --- /dev/null +++ b/examples/fparam/lmp/conf.lmp @@ -0,0 +1,64 @@ + +54 atoms +1 atom types + 0.0000000000 10.4280799300 xlo xhi + 0.0000000000 10.4280799300 ylo yhi + 0.0000000000 10.4280799300 zlo zhi + 0.0000000000 0.0000000000 0.0000000000 xy xz yz + +Atoms # atomic + + 1 1 0.4643750000 0.3949230000 10.2940500000 + 2 1 3.5348720000 0.2520400000 10.3158220000 + 3 1 7.2324620000 0.1228930000 10.1355050000 + 4 1 9.9507070000 3.2752620000 0.0515780000 + 5 1 3.3706880000 3.3152460000 10.3646920000 + 6 1 6.7449020000 3.3255490000 10.2638940000 + 7 1 0.0488410000 6.7351830000 10.3711260000 + 8 1 3.4651590000 7.0327210000 0.1006810000 + 9 1 6.9307650000 6.6957410000 0.1507120000 + 10 1 10.1233330000 0.1368890000 3.3011780000 + 11 1 3.3081460000 0.0219510000 3.4692380000 + 12 1 7.1162540000 10.0116820000 3.8228820000 + 13 1 10.2833810000 3.3816330000 3.5961620000 + 14 1 3.6750080000 3.1843190000 3.9967240000 + 15 1 6.9892550000 3.2920210000 3.6984440000 + 16 1 0.4982230000 7.2412900000 3.2778060000 + 17 1 3.5704530000 6.5520740000 3.4567950000 + 18 1 6.8032210000 7.0392460000 3.4280710000 + 19 1 10.3091590000 0.0994260000 6.8924220000 + 20 1 3.4370550000 10.3684960000 6.9731360000 + 21 1 7.2526880000 9.9857130000 7.1602920000 + 22 1 9.9882210000 3.8193700000 6.5101540000 + 23 1 3.3795490000 3.8468420000 6.9077400000 + 24 1 6.6164890000 3.2631720000 6.7142520000 + 25 1 10.1576680000 7.1165020000 7.2578040000 + 26 1 3.3743670000 6.9032890000 6.7801200000 + 27 1 7.1298310000 6.9588770000 7.2617850000 + 28 1 1.5422020000 2.1329710000 1.5503380000 + 29 1 5.6699180000 1.1054690000 1.7107510000 + 30 1 8.5130900000 1.7898790000 1.6984680000 + 31 1 1.9555310000 5.0886780000 1.5922740000 + 32 1 5.4747010000 4.8624430000 1.7339950000 + 33 1 8.9310980000 5.1557250000 1.8983040000 + 34 1 2.0521660000 8.8521970000 1.9591310000 + 35 1 5.0983580000 8.5203820000 1.4825890000 + 36 1 8.8847120000 8.4793550000 1.6471070000 + 37 1 1.8016150000 1.6971370000 5.4598140000 + 38 1 5.5814970000 1.4779090000 4.8725800000 + 39 1 8.7363780000 1.5159260000 5.4921280000 + 40 1 1.3323340000 5.5041190000 5.5846390000 + 41 1 5.4108570000 5.1845870000 5.4133500000 + 42 1 8.3963130000 5.5316890000 5.1707350000 + 43 1 1.6428930000 9.0357440000 5.3812830000 + 44 1 5.1413850000 8.5704560000 5.1667500000 + 45 1 8.9848480000 8.1466110000 5.0493800000 + 46 1 1.3577270000 2.2488970000 8.1097190000 + 47 1 4.9511170000 1.9240540000 8.3422290000 + 48 1 8.5515580000 2.2705350000 8.3764730000 + 49 1 1.7533270000 5.3976920000 8.8873150000 + 50 1 5.0125090000 5.4878990000 8.8070480000 + 51 1 8.6850970000 4.9585190000 8.8666720000 + 52 1 1.7618510000 8.6701780000 8.7299850000 + 53 1 5.1786470000 8.9149870000 8.5980960000 + 54 1 9.0434810000 9.0196510000 9.0660600000 diff --git a/examples/fparam/lmp/in.lammps b/examples/fparam/lmp/in.lammps new file mode 100644 index 0000000000..ba2521bc77 --- /dev/null +++ b/examples/fparam/lmp/in.lammps @@ -0,0 +1,25 @@ +# bulk water + +units metal +boundary p p p +atom_style atomic + +neighbor 2.0 bin +neigh_modify every 10 delay 0 check no + +read_data conf.lmp +mass 1 16 + +# pair_style deepmd frozen_model.pb fparam 0.68938740 +pair_style deepmd frozen_model.pb fparam 0.25852028 +pair_coeff + +velocity all create 2000 23456789 + +fix 1 all nvt temp 2000 2000 0.5 +timestep 0.0005 +thermo_style custom step pe ke etotal temp press vol +thermo 100 +dump 1 all custom 100 traj.dump id type x y z fx fy fz + +run 1000 diff --git a/examples/fparam/train/.gitignore b/examples/fparam/train/.gitignore new file mode 100644 index 0000000000..b5cec52a88 --- /dev/null +++ b/examples/fparam/train/.gitignore @@ -0,0 +1,5 @@ +*out +model.ckpt* +frozen_model.pb +checkpoint + diff --git a/examples/fparam/train/input.json b/examples/fparam/train/input.json new file mode 100644 index 0000000000..aa1498b06e --- /dev/null +++ b/examples/fparam/train/input.json @@ -0,0 +1,62 @@ +{ + "_comment": " model parameters", + "model" : { + "descriptor": { + "type": "se_a", + "sel": [60], + "rcut_smth": 1.80, + "rcut": 6.00, + "neuron": [25, 50, 100], + "resnet_dt": false, + "axis_neuron": 8, + "seed": 1 + }, + "fitting_net" : { + "neuron": [120, 120, 120], + "resnet_dt": true, + "numb_fparam": 1, + "seed": 1 + } + }, + + "loss" : { + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0 + }, + + "learning_rate" : { + "start_lr": 0.001, + "decay_steps": 5000, + "decay_rate": 0.95 + }, + + "_comment": " traing controls", + "training" : { + "systems": ["../data/e3000_i2000/", "../data/e8000_i2000/"], + "set_prefix": "set", + "stop_batch": 1000000, + "batch_size": 1, + + "seed": 1, + + "_comment": " display and restart", + "_comment": " frequencies counted in batch", + "disp_file": "lcurve.out", + "disp_freq": 100, + "numb_test": 10, + "save_freq": 1000, + "save_ckpt": "model.ckpt", + "load_ckpt": "model.ckpt", + "disp_training":true, + "time_training":true, + "profiling": false, + "profiling_file": "timeline.json" + }, + + "_comment": "that's all" +} + From c408edb540bb16e056a2b27d3e1850bdb1260eae Mon Sep 17 00:00:00 2001 From: Han Wang Date: Fri, 12 Jul 2019 09:03:53 +0800 Subject: [PATCH 33/33] mv data/water to data/ --- examples/water/data/{water => }/set.000/box.npy | Bin examples/water/data/{water => }/set.000/coord.npy | Bin examples/water/data/{water => }/set.000/energy.npy | Bin examples/water/data/{water => }/set.000/force.npy | Bin examples/water/data/{water => }/set.001/box.npy | Bin examples/water/data/{water => }/set.001/coord.npy | Bin examples/water/data/{water => }/set.001/energy.npy | Bin examples/water/data/{water => }/set.001/force.npy | Bin examples/water/data/{water => }/set.002/box.npy | Bin examples/water/data/{water => }/set.002/coord.npy | Bin examples/water/data/{water => }/set.002/energy.npy | Bin examples/water/data/{water => }/set.002/force.npy | Bin examples/water/data/{water => }/set.003/box.npy | Bin examples/water/data/{water => }/set.003/coord.npy | Bin examples/water/data/{water => }/set.003/energy.npy | Bin examples/water/data/{water => }/set.003/force.npy | Bin examples/water/data/{water => }/type.raw | 0 examples/water/train/water.json | 4 ++-- examples/water/train/water_se_a.json | 2 +- examples/water/train/water_se_ar.json | 2 +- examples/water/train/water_se_r.json | 2 +- examples/water/train/water_srtab_example.json | 2 +- 22 files changed, 6 insertions(+), 6 deletions(-) rename examples/water/data/{water => }/set.000/box.npy (100%) rename examples/water/data/{water => }/set.000/coord.npy (100%) rename examples/water/data/{water => }/set.000/energy.npy (100%) rename examples/water/data/{water => }/set.000/force.npy (100%) rename examples/water/data/{water => }/set.001/box.npy (100%) rename examples/water/data/{water => }/set.001/coord.npy (100%) rename examples/water/data/{water => }/set.001/energy.npy (100%) rename examples/water/data/{water => }/set.001/force.npy (100%) rename examples/water/data/{water => }/set.002/box.npy (100%) rename examples/water/data/{water => }/set.002/coord.npy (100%) rename examples/water/data/{water => }/set.002/energy.npy (100%) rename examples/water/data/{water => }/set.002/force.npy (100%) rename examples/water/data/{water => }/set.003/box.npy (100%) rename examples/water/data/{water => }/set.003/coord.npy (100%) rename examples/water/data/{water => }/set.003/energy.npy (100%) rename examples/water/data/{water => }/set.003/force.npy (100%) rename examples/water/data/{water => }/type.raw (100%) diff --git a/examples/water/data/water/set.000/box.npy b/examples/water/data/set.000/box.npy similarity index 100% rename from examples/water/data/water/set.000/box.npy rename to examples/water/data/set.000/box.npy diff --git a/examples/water/data/water/set.000/coord.npy b/examples/water/data/set.000/coord.npy similarity index 100% rename from examples/water/data/water/set.000/coord.npy rename to examples/water/data/set.000/coord.npy diff --git a/examples/water/data/water/set.000/energy.npy b/examples/water/data/set.000/energy.npy similarity index 100% rename from examples/water/data/water/set.000/energy.npy rename to examples/water/data/set.000/energy.npy diff --git a/examples/water/data/water/set.000/force.npy b/examples/water/data/set.000/force.npy similarity index 100% rename from examples/water/data/water/set.000/force.npy rename to examples/water/data/set.000/force.npy diff --git a/examples/water/data/water/set.001/box.npy b/examples/water/data/set.001/box.npy similarity index 100% rename from examples/water/data/water/set.001/box.npy rename to examples/water/data/set.001/box.npy diff --git a/examples/water/data/water/set.001/coord.npy b/examples/water/data/set.001/coord.npy similarity index 100% rename from examples/water/data/water/set.001/coord.npy rename to examples/water/data/set.001/coord.npy diff --git a/examples/water/data/water/set.001/energy.npy b/examples/water/data/set.001/energy.npy similarity index 100% rename from examples/water/data/water/set.001/energy.npy rename to examples/water/data/set.001/energy.npy diff --git a/examples/water/data/water/set.001/force.npy b/examples/water/data/set.001/force.npy similarity index 100% rename from examples/water/data/water/set.001/force.npy rename to examples/water/data/set.001/force.npy diff --git a/examples/water/data/water/set.002/box.npy b/examples/water/data/set.002/box.npy similarity index 100% rename from examples/water/data/water/set.002/box.npy rename to examples/water/data/set.002/box.npy diff --git a/examples/water/data/water/set.002/coord.npy b/examples/water/data/set.002/coord.npy similarity index 100% rename from examples/water/data/water/set.002/coord.npy rename to examples/water/data/set.002/coord.npy diff --git a/examples/water/data/water/set.002/energy.npy b/examples/water/data/set.002/energy.npy similarity index 100% rename from examples/water/data/water/set.002/energy.npy rename to examples/water/data/set.002/energy.npy diff --git a/examples/water/data/water/set.002/force.npy b/examples/water/data/set.002/force.npy similarity index 100% rename from examples/water/data/water/set.002/force.npy rename to examples/water/data/set.002/force.npy diff --git a/examples/water/data/water/set.003/box.npy b/examples/water/data/set.003/box.npy similarity index 100% rename from examples/water/data/water/set.003/box.npy rename to examples/water/data/set.003/box.npy diff --git a/examples/water/data/water/set.003/coord.npy b/examples/water/data/set.003/coord.npy similarity index 100% rename from examples/water/data/water/set.003/coord.npy rename to examples/water/data/set.003/coord.npy diff --git a/examples/water/data/water/set.003/energy.npy b/examples/water/data/set.003/energy.npy similarity index 100% rename from examples/water/data/water/set.003/energy.npy rename to examples/water/data/set.003/energy.npy diff --git a/examples/water/data/water/set.003/force.npy b/examples/water/data/set.003/force.npy similarity index 100% rename from examples/water/data/water/set.003/force.npy rename to examples/water/data/set.003/force.npy diff --git a/examples/water/data/water/type.raw b/examples/water/data/type.raw similarity index 100% rename from examples/water/data/water/type.raw rename to examples/water/data/type.raw diff --git a/examples/water/train/water.json b/examples/water/train/water.json index cc764270fc..11eab3e429 100644 --- a/examples/water/train/water.json +++ b/examples/water/train/water.json @@ -47,10 +47,10 @@ "_comment": " traing controls", "training": { - "systems": ["../data/water/"], + "systems": ["../data/"], "set_prefix": "set", "stop_batch": 1000000, - "batch_size": 4, + "batch_size": [4], "seed": 1, diff --git a/examples/water/train/water_se_a.json b/examples/water/train/water_se_a.json index 62a52b5adb..28d11c5073 100644 --- a/examples/water/train/water_se_a.json +++ b/examples/water/train/water_se_a.json @@ -42,7 +42,7 @@ "_comment": " traing controls", "training" : { - "systems": ["../data/water/"], + "systems": ["../data/"], "set_prefix": "set", "stop_batch": 1000000, "batch_size": 1, diff --git a/examples/water/train/water_se_ar.json b/examples/water/train/water_se_ar.json index 4a10c48047..e2e1a2ed4c 100644 --- a/examples/water/train/water_se_ar.json +++ b/examples/water/train/water_se_ar.json @@ -26,7 +26,7 @@ }, "_comment": " traing controls", - "systems": ["../data/water/"], + "systems": ["../data/"], "set_prefix": "set", "stop_batch": 1000000, "batch_size": 1, diff --git a/examples/water/train/water_se_r.json b/examples/water/train/water_se_r.json index ff1f37e678..c577047189 100644 --- a/examples/water/train/water_se_r.json +++ b/examples/water/train/water_se_r.json @@ -41,7 +41,7 @@ "_comment": " traing controls", "training" : { - "systems": ["../data/water/"], + "systems": ["../data/"], "set_prefix": "set", "stop_batch": 1000000, "batch_size": 1, diff --git a/examples/water/train/water_srtab_example.json b/examples/water/train/water_srtab_example.json index 1f9f832278..846017a24c 100644 --- a/examples/water/train/water_srtab_example.json +++ b/examples/water/train/water_srtab_example.json @@ -50,7 +50,7 @@ "_comment": " training controls", "training" : { - "systems": ["../data/water/"], + "systems": ["../data/"], "set_prefix": "set", "stop_batch": 1000000, "batch_size": 4,

r>8$ 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 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e8000_i2000/set.001/energy.npy b/examples/fparam/data/e8000_i2000/set.001/energy.npy new file mode 100644 index 0000000000000000000000000000000000000000..85d4cadee90750514449bf948663abe9a854511e GIT binary patch literal 528 zcmbV`+e=e%0LK?8!CrhQD1&f5=qzSzoO6D+^&+j3Wfv%sC>ATHVJqbv3A)=$SQHlB zD5!>5J=DV*(UU>=sg)(jh=_uqU?LSkwv=!If%RPy7hVeYs^T}ab> z!tis_FzN_8>J>F5iw!x$*e^x!i9^=sIR0Dm?xkR+N6_$C@h2!~8J5h>NCx~*tmLp@ zz~{(2G$egjD^fQVJDLRZ&Kr7!AmQf=*xXkRZoS~%S%KK3xjm%_R3w$nis1>#!%Gf@ z*M`rw;-k;deoE51O7OgBGudZTJfsj?1#hb~Q?oXEqMD~enxQ2Ql`n=jS}?d+Q}%2| ix;4cXi&s}PYvwytr3Cqe;Mjv0$s>xf%aYvPIDY|+;^a{P literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e8000_i2000/set.001/force.npy b/examples/fparam/data/e8000_i2000/set.001/force.npy new file mode 100644 index 0000000000000000000000000000000000000000..f61db2e28db6813f9ce452e63e2b27e97248404c GIT binary patch literal 64928 zcmbT8`8QSX`~Qt8Qd)7KX?0weSYoD|3``Xv@`Fva_Y0>h9i&u$?>=g+z*t9ud z<8Fi5+6Dq|O9K;a18@J`dv<&6Snt1k)8_x3pYOFTU^9O{V2jtz&HS;Ug}J$jwuRMn zBNOez+W%iKX(mk^-#H{x+_;i1=(`K!v(Av}F-zG+1~ufyltiMj<0VuZX$iZmr{S7q zq1dzDo?9?C5{bAKj{Pr+^;I1URuUJ;^COb%l@(gpI4P5O)U6cWdYnN8k zQEfk=F`sXft#2PP3&Y}I@Ld=+x^Mt=$`ml_?JIOo6GegR4H}}IihhnO!OeUwm3==* zmk-=BB^tKHanzckQqAdD*i+X=S~GD*uM2V~l^!7}0i9CNmUlG1WA zz4Q?#JiJAwCfo&`L{So@V*}l}Wk~Kykm(}PsNElf`Lj1eTWUK!@v4c=+~7{uMi;|U z0i_;c59sA_&Y0r=5w8TK(RK1cLYuaG!i&?>iA8ES9123R_r-5!W<~?9uet@mLg7?-C)vE+55w0V#l-S1GV_ZFzV9xCFC}Ae^N0u*c2p8k zw{#SJehLeAI$?HDD@MLT44NU&x;<~e$=wf8`(uwVD#;lJW21rVBgA;dcs5Ty0X&Q! zP{9uo{H=WlTr0AG3(7&!--l3lmk*5cxeZ@Ct6)b|4(dO+EZ8MsB+OayoZec%;@sj| zTE454oPKc__buEBr548+)d5vbRW=G@;|;lwH}u&V8Yl6vwhf7PTnvT3MTq^CE;Kb5 zhY`!}L89b-ki4D*EiDElIIEKW3`!(->k0XAA`5JczSGLeuVl6C9PpZ;L+91s!^4a- zd=4^43GFHH-;z(vnuIlgM{YsJy9jKIJBAls6;RSL6l+tbfxVL>o#kvqM@C6-J*jax zPrZq}HjZOoZpt8+k_EK2*&h^rm!PAD2|Z+=0fBp@xR^&AswxbV@onY8kLQlq-V6_d zsk>cR>t&lD`KbY->}R5(W{NHo1>AalE!JHolS;hLrS~W61IOMcL(`XoSM(gt-Y5pj z#rF}JdB>rp_@MCdTO0EGZX6N3hy%sEbg=B0!W!>z#KHJ>^5e-|>`iIMS65EM$-_Zt z)Z&dOQ;2{4EU;Bd94v>5X{H+g`CTX6qWhNQ>-gYnQo&e`-AP9|N#Q#=j;uZ$POe4I z#J?&R!S_r#oqFDcduY*v%cq|f#;&Pmo+heezP1f#J}nOCR~@UHPcli(Br6}g+g z;xHii7@W6J$G|;r$^8d~_+#)7%-UK4JExqY8^@d=8`@J~^MoN`iIzESEuX@s3i#sH(_L7JQ{2b!-nEI>U%a0>PQHxh8oboeQMmTEsyAm6junVtpR&Q5$@W=7E05S^BE@KPy9 zNq-i*j%9=FDhqshItH4jY{Q`$btLg*3K3du#1E=fv}r~HJS|%W7d}?Pf12g^al&PY z`jAXJ_qF4TQ+wdIjx0O$vWl7a`v!(E+0f(JLJX@;!fWjWho|l*n z-%H5av)ROojKx!9F5=vTMezQ>V)CKE36oVuK}^bf94k-YX}}oT>Ao9xjt-|4kG|p8 z@p`y-N7MiAonG%3&{1a7u<*xYQrl(;r}~1)fJ_d2SgMCj%8KwJB?p)Pl*4~>Ps58o z1+F~t4SZad#`vU(uyKENIfwTG?#;{#b-lWSD12*x-$Xa#vR84!MMq8vxz%T3w!jcX zG;)A-3k3}gKd^s#1uC4?;Eif0m0ev)yQg2k#XAPUdgnRvWzSrUscs>nz8duH(H2}W zYBdWhJIQUsiQG%!CRVeog_aK0W46~ZF#oCq&0d3b%T-G8P}XT^deI6Eft{FZ@tdy8 z%tNo}+r(7w7o0M_#{r7wTi3L zSA_1+g=qG?gGje8!j1OIT>GXhQZpPvo+#(wsr`oR`5VeaTMmht6~fq*{kYv`0_)Si z4kHS8;REXhEW33-;v+FkYgCcnD}LHBnLIhg%6c1sJF5%q&|}b zUDq3U*}Pb2JmVeXHPeerFx*6bg(<=HBh$d*r`#bT9TY+iYG%hlDAt3wmpb~f}JhYVA~{O+G1LF^u|xd+$n~Bs&qvX-#>o~lo z;Scy#gZh~q$M>a2*dvkgaNyl;%#%&SiT2*GadH`X@VAegIF^S?{(Fg)X{s*dwC;g@xlx-QHUruW=+~W$o0!-7!ov%{atp22>%|S8G%1(B_sp>H@Ys; zVk@($;u1cq7bgbe0`Zf5Cb7$q>frO*N~-Z|4>T;5!NGtB)IFq^tSkb!Io*`nmV6bsycuNn$e3bx z=W)3I)rxFA-$70LVz9g^oR(-L)BE%DP~CPsN0sGp_uoc1;(8M=nH=L5np}o6KhHz| z)l=k7Z5|a7Q^cQ@<%I0Q+e-Na;|mfW@0#ijce;@QkdQtj!&=B)5U8;4S?-6X=1PyCv9TtdUHsK80X z5QI1{eCcxuA6obb*R`L=1AEo!;Or#)w#)~@oS%?(@3Bz0SV&E3FGJ+@*SOz40uw7^ z;gU@l>~%ebPZo}1jXT_k@$+nexEzdanGRRCsL@>kz8JbN3%?}D38gBJ3st7y!*zrI z;ai0cIy<4AF0=gri>_oq7T*iS$e$)mjXeBWUI6y(vk)`n>C_u1!6_nB*i=74rS3`4 z!tl>nV^auo-%o|^j;q);)I=uV5k>2>BgAXlax52YfsjLEh-$_nNS^T(>B12@k|4_U zJs04vm6wnNaqxJ@YHE|B%gj*Mg4hpf@Y1^hP5LvzAqla_(FRW)Sb=(`%FN-)EZRwe zh@q1z)Tk-5)$31@%Dc0uo|8VVveF}~%v*_eTNPcodpX^!(Zi?<%wYk)-pAeiB5cr{ zfL|u)VbpR-R^k18qQp93&wOpZ-*RTxuWi5&=T|_=o@m@Q_a2J+dgFUy0b>@+;P%;K zjHc*B;i0baX92Ku3&LU0$%2cQ?DDeu7BD` zbkOb;{-{GZcOe1SlzpbEvWoaJYz`x8*(AKQCky5ls)L@XJ=vk80|&a3=<0h$TgrTl7`gDLbFQ#+qsn&W@vf<1I0B_!xd_OGLZPZ=rI|HBwzw36J*9 zg|ZGEF40b&{cpl+^0e$UoUI9geq#?9%lFlzwXWguOTS_DXftk-!WME)@&esCN0Ox~ z+UOo!3sEBCkn~E8KAMtM}@4ls1lSCToDh)=jC$W$Y6&qaaG^o&N$_uo*^aT2P2 z+tW>@l_+TuPja(8KwLMOgr`}Nbxu>j+dGy#>RxU$d7mM@yYnGN2oiDiXBP-NU5Lsn zgNc4C;pn)97`^pu-Trk^gxp`iOvo%JTMJ|1@1lNKH`t5&!eddPI}iU0SczDsgH4xAFu(m{-rZIdlcx>ifv%) zM)17a3hra!4<@`S1%H^|IMli zdEjPNheqpiF@J>vcm1dgjxH}J*R;$qeM}iSRj!Mw-bp~T&*GM*|G@U|={l#)MNDy0 z7UbrR#Z0$vb!x52@Ob0{J`BA|9xCo(k33IBG3QLk*PR9{8yy6SIsPywIS6;&GlQC< zUbufbk>ongfSMC8iTzV|R6MSQzk4QNk%A9ci%ftNk(;)S1H(^ri zK|FZ_RMhT)@>o4#%)w#2Y$`3Ro_Y~09>-#=);;oIc{=@^dJ}$YzXPN7BIL@bX|V9B z8yEQ^6ipnjld?@d^hx+2QB)p4k=?t=oRjHw%7PZEBHV{X53dQWi_Fj=pbWQ8SPgSL zjiBmZIo3{%XHwpt!i!5nsF-mV*>i6yDJZnUqxq^8aG*T18x`J2cfPTVbRngV)p71m}jKYPu}(5?G((c5sAW% zb*ssZb*c2M=W?#)s;~Ex36&HRHraKMUQa0 zKt%$rvA^yvZax1BPZvHB{*Vvi^{)nWp#Jxg3fHPSXE0-MC3E(ipi|iffHX zVx2Emf?%;YIEePbogp83!{ZQ$S0&=MIqN`nbvTaUa!`4aA0~6Nq0sdhzB9^zl*Y68 z)>nzD+P{GL7Y||Pi(XpzI~xwKlSFadWi%sBmfKTRC45|R9B!%v<273q*354disqds z3M>0DA^kJauZkD`=un5N=DVq5R{`DSu@k%8{LrDq0zN}Kc-W8BB|i&;{M&idUACIC z<3r*8Cs8a*UkZkq44sv8AC^aEf?tjY9PpTfUDrZDZkGf+9XW<4jxQuvuL7@!Z^8o` z6wF$rh5oX)a0G5+(~=$t_<0j_|9XJ&!g5f483KF%$#MISl*68MG4{t#1Lpg&GiYJ= zgF3$aBCOwDN=?3vqMx#qaai68%{nL1I*(~2`f)4sBijS7nrqxNdhld=1>9>i%XVA9fj?6TN|Sx;=ap7mOw_al}({xd{c_3n~$NrphiY$JYh zhasQuAy)M2KZ-q=7A`Kv_(c|6)$?D}%N0OV;D>Y8T)>K27Ou^{OOKf4Lh-5n ze0@uXS~(TSSeb`zp_5_a=mhNID5xD2z{1Cd^h@bzGAV2^#9iM>HG7s}|1>}7_lbsO zCo-|Zdm@xZy`-Z|t+`B9b=GBjBmFRYp>S?&7LosM2r-@pxIo$xTFbU^gJonfy@Vk1yliGn47UpUUjAzXxfUa}B1fm=3EeIP~ZsjBsfP9iF@nGtZ@> z@7X)#gt{H{WGqA5b6FT&bAq`$@dlWCTZ7k(^ANMH7K;yP;N!R$y6l}C+p$)GXn))) zG(9QGLhN0VW}FXl#q(gVswQVpp9z1xhe>RAG>no9fzDDFQV=W8Kl^VA-RpXwW=9?L zo6KWBEPhNegTs&>CFpc7A_{9_i16i7MsMpAc&u?5Qit+q53F&9y?Fi?_JP^-_PV zOWl}u{hmPTBvNSGXMHv!Rtaimwan!REj+c%W(IMFmfnO3G`c!!^Mh5@%sBN9(tP{kmH`M57^F7X}a@ZXgfVx+T$I?NuAAFUnmxO560_qqTI``(f1 zw`1tb+I&2^)CY%dN^>KLmq0w|4{;jTA)NO18z#+Mf{*Sivm!Q=;nsIqRQNc7mF~EX z5B@pO)iME$-@M!8$ghd09xwyHtuQ1y`yTPvok<;-D|oUd1~QL_V|0WFeB4n6U*6Be z$@`9xUWu{X28$@HpPq`9mvhPR=!sNkr4g&7>q)YwguuwChahMDQm{m(fxIuh0lGK) zaqxl*zWyKuL&vV*E(tr>^(L0~_F2*|39CTfPnRuq5o5bM{-H#fIA61(F{r~G7Cn3c zOPsQ5!rBjDc|ZnLpE#K|8?=Cm%Q1XqSPk;ivIG%#+@ZbJ46I*GgFThiG$>DoYU(WI z_op_k?N0=yRjXl8%nUmB_+e(gK5MTKN7&p}k;1Lqz!WLaHXNiy-!`+f z{W^TI-wBtg2zY7mUg|&uJv3FBJNE4;qH!WEkCA{i@BO(!$u_ckW-ulubx@O<`EcLs zCp~U(g8q;{0rO8rqG3oW`FBMUwLhm**GY|pG04InPenkj`3S5mc}3YM4XReL0IJiM z(@6h79PBX!hqtzMBl}&k&!QBc*J(n6+*=Sl1GxJ)6mD9SK*pU?X3prN_|m)&(>g#y$@e87ZMFlH!Q^dnGFPZpQAscn~(27h_5E0-Q9oA6=r1xvm|*$o{^E@Wa>( zL%;>%6|zX$>3?Loc{;dQa`g2m3H0oBpvA3YF-4>dMJA}>ZjC&lhl#pF zBZ%0`OTUlJ?8iG%?gmt&3 z+F%c_7qhM&rPdM34A+l8yE%IQeUMx^u&Y=FxfN*%p#uS_y;2F{>v93r%@Wrc7&s5Pal(aWPqHW zPH<*lwNQ`d)B8cS#62eim-}tT!2QRBMjjzF?nJNP>241^VReg051d5HX|iFK+W#|@ONU1@YcpQj17>28Bw38UY(FOdl|#KfwAza zVmg-1pMq&M^=P=c8S*|g;r3gOup|E>*l(BuA-diq_jDL8<7?#5vLWD4gl^Oov8 z*-n;ZA0Yp|DQ23-^pXonV%!&zdjc`jC)g_f5UTsinSUm_5IVhu9Oyj&U9MHsH*6IA zJ~a;0uCBm>C4ItavE4XPItEIg$3Tgokd7r}9seT$aPcu$lIIl7`-oS~g*5EGOJH~%!jn{Dxko6g|P6DphwuVc3jrff<> zCMkP3nwfSdMfh4%o2)F91-t4d>lM480G7v zLHwW&vqqviVb@15`tPH6&&3K%S38sBf&lP;)IbW(uZLkf z3&5=V81bP&_}y|AZrv?PBxW&0F>*V*@t`z&Nah*YyL}_r7CS-2u1FI9yd8!|Z=+t8 z4>2q!l^I)OgEyp7p$`ATrRxm`ZS_+I;IG8;2XDZ|` zpq1z;csRZnQ-3Mq?FT2Z+EWsoLWMBOSskwXtfrF`B4E6|6s!C96tg2Q6FvoZ!9aN) zgoZyPUnX6rhaSAb{*GQWP4R;3PgJqUpc;%;DROBUX;{yR)1^&*jPv^|^xE4pYCpA! z*N%KYJGB&luXcxvC?GZe-jSd8fva0thPyn1iKB%G(d=2m&A1i@N_`*5WIZKL+Blpn zmCVPRR)x_2XcRzvGJ9xlE5uo*GU~UpAbGkCEk4QDzmZbT_gOjFY_$zXi)e7$;})UF z+Y0RYT23EwAy8~5iy?=S@YvB1wDU5-33ZdG*W$bMQd1or=?%n77nonnVY)k9NV(WkexF~*+xL_>rPbTviu*?Bt^7u>KMo_x0gHvxSIV#x zKW{|;Y%x6DFcq%NdP#yey@YoUg5j-i8HSpjf{aoFE>PEtPSq?RVjC(Ta##-kj!T4L zgJxK}CXUOiz0D`j$9m=ti@J- zmE`@p+1Q^wm%gp7BNrDb6SF~Gc8la^YWq4~@S-=D)XGTUUX^grx!{j#x=3UmHP8U( zSD2Phbg?MH6wu@i3`IlT)Z{goUQhm_d(aC|ch|9qyz-eD((7 zeR>*{&RhdMt)6)0{V6Q^J{QA3M}x|E2dFx6k(BTGN#qJ0xv;E6SoUfzthQSZ*KB{) z319aJx2^Rc%?EaHhwr}wKN%x7WyKDvrf&)h>-;gG<^a$k0?sSqshUR%O>FsvcNYt& z{S_yHbf`NvEI-4%hHz@7X%E|&&1`_Y2Id6Z!_$#rAPAieudZ~$ZuMKdCXmMTOLq9y zY$k3mZp69+MRfm^qp*Fc39P=n4x8MzfX+K1+{-^oM#ii|XWb;i_qce=WCDuJF5`3O zW~^|c?7&IPns31vr zhRU#aV_h(9fgb*)lfW=-Fh^;4hHo7e2I;@e(fcU}*j zn8JYZ%pk_?c`Kd1SBE>0@Cz@@b3qBw%b0yift}OZM}MX|3Z*JEVCOnZ5Zf?7Y7cMV zCbava>qSMJ;8Tm<+q!F|JZ-UEmcrr5ukp}-ov`%dWZZMh@cb9_4EZhIDKL+o;^~Hf7k(3v#UP@k_WvRRu~_!Z7d161;9w0rp`vFp_u~)7OrNkTG>2|73ue zUU(0?3+D1QVl7Tyxe^YP*h8rj@4F?-abI}0M|++J{vA(Y^7~cv)Ov5irR>G#De>s> z$b-*ZUs2gryjCtcE6@|Jq*qQ_;WG8vP_rZj&n&1T`XN#H;_?c}6Mc+X|cP(^kUuEpKSdy`Z6Bv_?F`!x!#tM{ol4DQjWAWbY zpg7z?)2DP|APGbD+Y=yTLM8J&w3{9sF{Q(8A=F0Rg={Thz@pk32l>4E-I!*wUT!D8 z(6fdR={TB+GqLTeJUi;h2xWgo!pdb47?>Q&*lnnV8E0pKtN2XLn%}R@8=`1Rf)so8 zM=yEaF+%L@`Tp- ziP<>!ogd$G^Zw45i;SywG~=9TK!2B1&_U56*kEGInJEoH*~22Vsd@v3GX8i~sKD2_ z8fx>PkevCUN);q!u(-97R6P%YK}{)q)wYiu@0-Q)u{`cG@h0=I4DG&(vfJ)83*CGp zv46uZ>S*l^>>@REd-j+_Tf__RS{(~izT|-}Zg=VD{m;lDdnuHU$i~mmOJ`W61OLG1 z(m$$U-%~AGU=s|r#&5_6(K>Qi{0vy#j>g=hM}WD!3twi6V&wA(=nsBMdMnz6k&)RD zm~fQTA2)%~;)7(iAevNNkEE{g?aW!vDw63Ihf*2xuwkJ+XnXCaK?|Mn!l+ny8GMtH zDO0d)sx!Hso=8oe)H3$tQ(^h>Slqy^r_a-9VC ztGA+8=Nq!kE`=I(tMHzH8vFgZBKf886>L&AFuUuWScf7#Tyi7_OLs)V*g1yq@>u~j zQ}4&QfgjONHW-`yYY6?dP`Ku;A}aD(+I++5pc?EB<&Al?_|gZ6R}3Xf4u2pY&SsGu z9W~IF>!Qu~cA>kG7}p}<18I&YaKJDDSC7tyjDIq0x8edg=5m}oTU1OxIq2da&32p= zf@Ex4BV)TO4c-MC;gf4}@F3s~1Ux-KyR18zEC)mGwb(p3{%|YlH~9@AYn@QcL=j!A z!=cPDkUaO9N#bVwCBt${(b1xtKK`c&jk*h&!&3RUdfriS%B`wK|FUbfu{XP#U&24U^_A&I=W5h)A!!==AY-YmzLe9Ik&7iv(9@= z@CHkq7TZg&#g4^`N>0KAlO`x$7mI>zyzj7Y5$^#ZPF|Y><6d<^_J51K z_-45D#v6+^2~hIcSNdf9E>QRoB8*+pPL8jYg1)E!n59X&?25u&o}zK(nNMr|RA%PgiM)*tDbn}^BtCUI`Nm?g~0Jqyk)*#hy^lWC;t8d&cc zi_bTP&{e;}DN}nN&Non=$2)-L&c$FZmw{Ek@9{cR65f7qAd$bUF!UG)M<>0b_nh*v z@Ru)hCsP_yN^@cCmo#GgY80E)7=y{%IpM-uPmJ+;jdrGlE$+JrU(TBXS1tnegC4M2 z){#xp3gfnY4#v)AHU7LetC7!uX;3gU>(8d;v;TwBeq&gx-D_*4lgPiWB0LAj!>uwL4n zlUUV&H6vpo{|306kOVqL&rPzFo$(ixa&EhxI^gx{d%GpR!+=@lUbe2G8;Gc zs$U~bPfH*{&N;B_!da+jdq7K{nWJW40aa^#jaHJ=;P50ZT)Oul^INv{S<1pIYMUFS@>Amg5J+F;i*Uze7Dw_?f+vm z9zKMZ7l=4A1Z@RQ$}LgK^<>!iM{( zBX~gn3e~`){S)k3?gqI>eIVfbkYM$^Jf4xdh-Z{6x#5~PP!=XsG&{%gy;>U8I59<_sBFcvdN+mV zq$cD2ppA^^;0>~SSOtP|M{z!ns!6iBGFx7@0R0{HxaxV?$S&08boQ=ht*-^4cIH?Z z{b4SB9Uo3>4F-wUi9ndH?MK??4&#c892Pxt;@UF$VQF9kZg}2>`I=9JRsQm1%>^Us zcU7x)TtgJj_ZnoroR33?B3Dp(>IL%#g8_E9LR>>M`dsZIOXRIcaoKfp<*q+mTDgsT zxm5#3-ZzkYi()}b?HG-DWCj1ej)&YKdq}*ylPLOZ1|Rt`^w`wVVDxbxlP)O_<8Gg+ zn{s{}m;5XWeZKX<3jLpGS<@~o7o4WE9Ph(PlTx(ac86z3ZD7wOMTibhf@wPsFbQ4^ zrj#nfh^`vXS%^Ym{up-ly*w}wI-tDMLgKyX76xxx!1FdAA#t(;R@BMR-=kuQo|7TJ zt~fkz!$6JdELgW-Asm;zfFA=waC!Jm6h>{qjdCMYrY{H9x@mFN)={`R?LN=)IFYBe za;*F~0Ldl3IDX@1H28Ui81<~h_K;U-;w_7P%6D;TTd1PquS@?cV4rgom zz}7d- zxX7-X_tNUYEZh}py=1`n%m+dCy`4Bo=?{5OzY5hSb<#|g=R#k<8^SGM1!+8&U{U#j z3}?20zw9d}*Ww!^`%Z&9wYCHbUWSm$d1v5}b~0Tv>9KI*7a?`p%J*jyYl!-u9iS<3 zn4Y%ILU}%N8gh*!fu+BA@1l=PjW%Um+#ZwE?FP(1?-fo@~w*8Qvsr)P(a1wxw0uZ_{0Zdpu|Pg*-X48=h>d z;(5jMc*f}){B^2htj|Q@LCXZZBd-isH%rl*FHYBWjQU4j+j=lQJ1JC#d;4)2VoG@Sj61Uswj_Hb6U^BqHQT-1B77Wob zD);CjzIUp&N01xtrFmXm=qz@LoG}yUevJ3vS@-d9-7A5pSQcZWq&RH(a2v(vPRCX; zCG70>0fY7>-1T|2uvGUraoyVj>yI>19X%KHRVu)hqH{1JX&JE}_nP;}OlaxzVq(fV zvYKxW(kW*nang7jvZ%@f4(myiCog1J7nOa?;g8puk@tw9&ld1Jkrx>+ZAZgBMd6(O zMI1=!gg3Jsag9Va=^u9;ZYf-$_D@IQed*EA^Z67wqCNU-Hm1qGwmA4<9sbJuL_|cZ z@nF3OyLCquSs`(b4(@$MrN?@ZcVEuqtzZR!=w#5oF9*q?gShQv7)+PUz`ZlavLkc8 z!9x{QR5OUc=ZZGMNef(Hh3+Oew7CRo9qs7UVk7F=EyZ13Xh>8aXOZ<|HVM}(n*kvn zO6dCGCq8H>BYOF~2feHjUhFqwx4Tcqg)W1n=2|z+I^qYf_epXgNo$E)?NSK6)<8G^ z{wnn+Zgj>N6A9kAUv5u!~mkrNH?kTKc_&6mO<@frgM(IHlY4yZms{u6Qnpuk+nW22F|0}>5aRVWaz{ftb8zx z+x9(!jg?Dj;jw4L;^0lX=WZOMmUJG9j&Lxqeo!#ucM~L)d?a^cS@!XYBREQa56b6- z6W#4naBdZ!4R5S~Nj}rj(Jc~w8g-F-)5g+>iN%x~^9_mt};{@>-eIrKDZQz@jvTQ&5CqyYVYN(mRpWuZ}XF`v`E0+l}${Y?z{ zIfX7%>V1gQN?IXs|9NI`bp~X_OknFfZ=zA3B1aCm;F04c*yE`V#)@~S*T36nM5d8r zo62c}#v&56S`zl(S3u>PN5Qd6f;^XB$aKCdfcM=_X#draQ@FRAdcM1FyM}Q<>({?X z=;Fu3e?b!5a&jY6cSfVyX9-m4YQxRD2FdwzBgDygHkeNn#l3$98B^)&#B%Dzha}jD|2jd8S!`_=oPmA4=h{^1K`r)l6iZrhcPo zPH(A9P(8icegj|cX-4lnH7KgxKn*%Y>5r^dd^;@y*K%*5e(N=1qTM(T{}d2 zzFia!sdv%4BS+wvc^-O}q|l5#N6;d94cnYy#=ce9juZcLAU)FWX~fDW5EZ*0e~s(F zvVl%87&al3`QPUBtTmVsGnuyP{wCHlDtNYbCsA1a6FT%VX;^I`&YgIG3mT`6^M=2H zLD(W;|05M_k(?%U{}xRzN&FH%ofs(u|7EN*xlCStZlfPh7@|bcEn+yYo%}nbhjD79 zl-$?hHtp4gk9*!Dx9Tz+c{T||)zitB!g;7EsRY_aiS(C=6f|h16Xn>qIDO?p2$?cR zC?QI)>g57X&!rSUPUQ2y|5mXhYf;#${{`yA4#VVbAwTg&2l8o71eO?O*HlRIf{*HQsDMI&4d+|!LZ!&AE+JB zXLn7IV-9WhhGivJ8NIN%aHIP;mVMWSKD8t8^`$FbXs&_tZrUjMd~H1glg^FU8Xc%2 zDO!hV(LNsOV=QT;dQZocU3bUHdBvhBs=# z`p+5YDz$-n1V_+q_fEls`an{8EWMU)IYR6lFTKm1_z{;Bw*qmep&lY#|P!a?;h!+XtJfcch4 z-P8N%KgVLU(#|74TrR+a7=0WG6tL?|zmV4#oneXGT#PoJ##Ch8V^oe8l7ZFVsjH+5 zjjm0j_NNyR#~G*Upy(RCz%C;nCyEQdy$)l}_6Nb_Q#YvX$XB?(L5OSrsBpJO?*UzF ze|$GN7d^hk;pJ>eoOjHZd(!b$C|=%57Jcf(!o`8G?9X_3Ad-ne&#Rd+=i5n8jvjl` z@hqO@^A(jpH%ac(U}0+gQham%5;@m=0T+FeVJ}Rvg$q;GQ)?A{y8BNNES2B^sD>}N zx^g*$Uy8u4Khr^tpYwWieJ9uXZZ8U+tMI+gZtM@9h98$Jpy4-uf9-!qjGD)A-*&Bq zRp+AN$<}4Ge`-G5+7yQ=Z_dE0uSacfw4*{&pqD4 zduCH`Q)3bSa^zVi`U@wHii3uxiI^y|8mFv+~~y{A({Gch?PmWPZ0b z?H1wc%2McIJ7>Cl##Hu5Xc@`b74_aCtRGgH_+XFP1Ye+MLAQ0jQr2ec;GF@0yX zF|V?ZiTqb6l=o3)Yi@)fmn6^HPP_xl>-jmJrQ$r3UPS{NSK|6hXW-Pw!+alIO%E9- z*0t*V;paacsHf;e@NSQ%$pz!VVfJ;jyDY{|n|Pa~D*NIc8p78!HF&FdOOUZD2GwH( z{Om_3eQPm;UKpJWEn0>`_4u{qi1~f`NdFj}Vez6aU$+^pdff5NZ!tP*i!*IZh`zIN5Q=AAl_Z7%*Ec>g#HWq$+eff2F!3qlhCp3GWQ(Z=X;k~M>k{MAyK}L%z@Gu z(lDBlp^=g2v2>;<>PDHuu6OTwe@2$umiP&Dv@E!%MQ=%sl|CJJE)8rg3&@q0mk{u{ z6*v&E4tqq|nSC8-?LQGm>#E}9(N7qMqFb1=B8;ZIPUUlwW~QaspZk`y8>jL<*PR6u zkmr+yJA8H6S>vVf`_Oixg6d?i@!5(ScoI64l0!Yq|@FAq zaL>HyI8~z(j~mKxGv_DKpL0slrr#bfHHt#|0Y2~0JH+$%7zY>1o1xKR9F?MC)A2Ifm{a(9=d!&%~al#8QdetOIIXo zBWhtFZ8X@9Nvv7prH|}d{3ajRgNjIOAb?_5fhr=ZEgndr|_I> zdlvIo!4^!{rwCdkOOWjlWn12>Lq2#(|-le`I-rR!Q9uv&hj{uUqI2s!- z#L*Xb7m=x@Ba}%!it8Tb^BmGwI4O4>&l;$qgUxBeH3z^wpCKa3CD5`X);JWShkm>k z>#4d%pQ+UF+FXHkW@hjic{_cUZ%qev7m$`wHNy9+Lh%YKM#{$eF^*Oi+&Y&mSaZRK zJ{zA&f_%GRypbKo9PuYzH{u1&N6rgR^*zU!)`QIDhyg0|KmkV2_8NIPLx!{5@rZRIdb^o$e1MP4YORohXUxg8SHE^WN0S;b2 zNyMB9?dsA-msgD#@T7u{+b+)jahpdPd8Y5G`4X7k6G^U2+G#VRYch`G-*iy~#wN{S z95jW@VUovde*^OT$OLYKvl&gz?XOk+ae%J0X``IJBt@GUhYWq0e_fV6$O6QMlW{*UNBf zbLbKpKVJZ%E}PL$CJs&??}Zx|UXl&Zn!({#3~oz23Bv7{sZIJ!wo9EMH};FdvF|=$ z{%ABN|2#{)r-`#0TUk8U5{5;!156N~(^dI-p!H5S*0g**;6`yS!EqF8wj;l8@8S$l zd_J1_@=gofH;co0EhBDhP&jgv=b@Na4ms&yjHybEV6ULV73Q`Osa7#)J-HZ-w;SP& zK0S8L+gNPrPQwY0PU2194`{Y^8_%~-hchlyg(`VGV^{nX-P*&2s!po7&b*uc&u5&m zJqN0`8}jQWf}bNU7wXSHherz!qHu=}K%*B*Z`#I54#~snxjOiFSeX@jqJwG1Z>UFg zEgaaN0N31W$nXY#!8J`KZlgg7jVN2hZTK;kt#EFGJ!xUsT9!_p29EGN^d@l2HRbHH z_mGbJ3xy@Otl7yIcJRJ`OWlcCkBI8S+X(NA>CR8f$Udbr_~~dP{TjJmxXO}&dxO>J z{bL8&{W6Ql7_LDp+c{*>oNNF0{{=qZw6V=#E83)~!QCC2{P6NH-6NBR{h=GdX{;Zd zx|qxKEgEEY>5R2im|Q^|nl#WV4#4j5QenWfdfTR`OhadaO3T)y8Qx3V%K5|t=3m6izib?z2T8d@|+Q<2h8 z8deAiA(fVlBxUdSb#62%$%^XJKnWG4L>j;A`}+et9&hh)-RC;z`Ffr;``~rde0ERR z1CI{aL+#*XEL*vYf6(B7(=tAjLz8)I)kvWyc1OT&y)c+LHkF2ND`PX%N*F!Ak>-u+ z!jMZl(LF;&2)?%;4Nm)Ese~#&xYmX4Dmnx)J5GUtIOB5qY{2?QBI;MNGb%L;mE}&u z8}wuBlO{1Un223s!*IT@6(~&`#N+$3sr5A}VawRNP|Cd}(Q6~%mf3GLj9l`ReqU}tE7`10apn(>x(%Uz`sd)a zWC)x*@5nc-JB*6|9*NfL>VTqWENJ}b;5wlK%Pcj7S>LWw&suA|cBKl;e~-h}n->ZO zVaohX*Uu36Jy$dku$(CVS|Q4Fm;nA>m&pKgCaga+NM^2ahpcij;T)@Jwr#%yeKk^o zQ_LfHIpr1Oy)HxfD?7ZRorlTIFW_S9EqaLt;`bCKynb*zZvAaSdZQ~~`|(S7D=deu z^m#{$n?_=@&r&pUU4r{wJE5J48P)JlqISk!`11M**rwWz`mx4z`G5xfzO0HkOGeWe z_3@~x;R!Qr8>zrGLr8))iH;t@&cw4UyI4~yL2`*mKPeQG z3s}}+2wn=%AnMvJu+Q10puh$cquAgMJ z!dRGS8wlb3O|bpq#nPJdcj=OM^Kmgb$a#HVOZL}z3I9DiLxzptLN_kd#*w~`&#St$7)1xz5?93mpZfbkRFNr zFAP3x;GouH8CVRvi6^G`W7p~oXiX^u74u-Io4f+Q7S7}6o0Qvi(r1wi^jzz!+*Rkd9}3whsBxj4bV?)heXi0aqsBeLjtbpG^Uqph6`4EiwO2u z660}RIAWJK_)IZovk-q=Q*)QTj&foARyvfNUycgvSw2mxmJT}A!q(B_;p*w{H1OM1 zoHN^4v~9;-7TwKCB+O8uvU`;_n`Df|*wL(X!nStWEQXu2na^aCR}yGL%5> z*(03vw1Q5&{DO;Be1q+&H%P_zGNc2aso~XUgtwQZTcCmlypZPuRh!_06%V~<1-yOb zG8b>B!Mq4LIHlQ}JVzfID%C|QTeRS{^#O7>b1VwcMR+VG%3Ai;C$ib@0=ld;iIstoJ*U z7A+G;vw~MJ_ugCj-^OfAkJb>D{QgR0HvYu_&L1Y}owMQPYY&lG?LMj+?9cd?5yHJQ z%ZczLnvPWw7kqxn(n&e}L}g_snPQs((caB;org33(C{Yij(G=CALfJP;{@*G6&*H9 z8$#Vz$C8O9iv?~%1eiP2FsZGQ9_5a4fyj}0Pr2{ii&T}R-+!@BQ zAK$6Xcnz>S77tUab0FxEzEIing-j_q4JF+IeP^RZjpiO>GaObc1Y5%|IVaX{J`9(( z+{2#4u~<8JkoM4QYVz9!Qa*nLxci0PlNihHZ#S`a##@?GFpoStcpEg&*sZg#tfhC5~mtCmlJe)lD4I%jxn#y4ruf5qG{7+3G%(F z;1HezQO5#t+qKV_UvdR?_pcLlEmiqW-I;jNp`O!?F(*r}FQf-_<-sSZjGP=Vg=uzQ zQE^NOjV&A_z|2!Hz2XbqFyat~Pd-D8x?8QNsFl(5bxxg$E;X_O_oyOmct*Xt+ZzM_|tcJ3-Cc7NRaNRxR#6e7m5*ZD<({AzzEh z>$OM0a$RR>+sjt^XLzaTX|XDtzHmllvdn}SIZMLQr4P~0rUV~c{2%uehD%cCGsi#{ zZt3i!16N|$d0PWg4+_Bi^&Q-JwHktSH)Cn!9Q?ZV1uR|o3N8FjaT`KP;GBd6Kjusn z*d7`U?+d+%=VuF85L$}nueXBh(I5zLPsGZwNO*ZS3=XVvz`9*sWLD!m{tKI_eeror ziOg!@$rxjNqg5!{rCdR0cG%(YA_0`#x{0MkJrLb6deb!#np{p$lTd(lE@z5hfcr&U4XboVi);RqV%%A&2c z9F5TJA-RV>aCLUGQBB8^Y)w4{hZc?|Q?~h#yp;1K_d^Ne`BkXJqSu^9NCr&y3V{u8 zo|2VEX3{;bqu{*jUX&XB78cdLfcXn%fy*j+;mTS=@VT0SuB_j-I69a{s_US-9)&I=k^6Y zsVd@2`w5`PIQn4meBto=dh0#gUC}yuI%eKB;Sb(Ov0hQI9e#E+ z!Pc2AxbCEg?s9!a)jqYtqH_Y~?HMK%MirCmSs&5%(0bgqO#&wGJHcx6CeVF6iM;Kd z!Aopo&$@?=WbV6jC~-kSaLz0tIjrX%_jnNNUC%*hwY#ubY9B0rJzkj8)r=-1W36q* zU%>#4Gf>jt1=nyl<1F$~Ur!tN>-oT+epg8TDF<5}I@o>xF$+(Bqs`gjSQ=AIJXt=@ zRkjk>g-n1!X~MqSBlt73xN?i{T+H~&tEia1472@ZFw{u|2X9D#kLhM?a14QQ%Z;#dCBcW@V(>9BjU;IP z5&hFS0%Z@z3H2o(nR|leEIXG0f9)p7B^wA{0cY?|tbo~1E?@BQhde5(xCGm6|uT`5wxC8pl*X_fe&5?`6t)VD|=5uSNKn`m)J$+`yP;; z4l&sJrhxj^-k=KYmehQdJ&y4b<6FGekm>j8>046+>ZTfjZ&m&fql5-_U)vA!uG?ai zK@*6pAEz_XzcW&B{SpMNBO1V3o%IN>q|(AiQmhBY zyjgt>^uPxta#6>MU#NVYTYC8$_3d?pq58J$T%QZCiU&pQt4Fi5))4;ITVT(UqbNVo z*ZN()7MPny5ckLp;Qa6#Zq5XW@>A&!I z;t1>Ws)sS+gDk8Un+X*qF(k`l33xlJ!T7BW%&)MK^9eQ~GuNh|P3=uQHQtn%xw_DU znv==y#}%Y3J(id*?Z%h=<7wOEMAEtOuweZQMJ3Aq@awA;sEr@Wzf-V;TjAU2x%36X z7jH4)P0KU-J9Rh)igwa_>lE3%Wfbu~5=)mP1wxbU9Xc58P6~$%r5EN#iw=e_rZsu& z{ZZ!8(_0sBt-b(`ft6&^>}c#3Kh1|q#{yR{1&wE)#RSb+u-(KE`s$vOl7h?FXiS>4a1i| z1*|C94Q|sL8H18XAL;Bu2kj~luPDWtURSZ`MxzK;UZKw(9R$~NF`)MJJ@)LZwjMI@ zAJ*8|(KqU4+_H!HaAsLMZ1j^w)twhbH(JkAuPG&*J^$_5S7KHrd?8y^_M)9qsgvqduMpYJ~x1XK1v=d=MDb>I}yJsjC%B{n!lTzv+m_GwJ27 zIy$gZjz5xi6Vn}6SPw5sWHYW{y2`E@&zu?wbGp)@x%xd$|Gf#8{t$yl`=YrK*C}oF zv>_wo3h3^*p(Nx-3i00?jr!SVM7cUV&XKF*+S5JprP^mSvdtn@wsYv(jNdfM*M)RT z@(j^SquJjUa~+@6_=vS582eKS^)6{JgY~p7DY`&&t_`gAJqPA@U*q@d*Dz#C3NFyp z2b=C!v_&kN-Y)fI^@tq#byNXtEtWFBUlHf#HWdpwW2`g1hBGEbi6-ecwR#5UTlH3 zABAL(i7fA!aGp+m5>6swdq|?IEzMTpad7Q5qHrjJNPj&=ChsjJj?bHD(Ec8}?B7yY zI&hU}_50C_>75Yd`PjO>!bd1EJV$3Q`i|>fe+D=8A2jw$Avm1bPeiBhp@DV+6qytL zmRKE58*&Pd9h`?}y<*_e2Q8u?@d#@lC<%+TpA!RzDX?kdI-zPuAor_&FZEUkgOY+< zcqq{VU*ZM4Cxv)!TaYlTXAai3#lY{sb#TX9mR}>~#r%Z`+%rvHJ9!2Dw5tR+Ut`Y6 zGI!Cag(=wY?vL?r4%1)GnsihAWhgxq2v>BIh*-udaBllS{bVk~)8dV2(tL_ITNwyj z;-13kFP^k?eVgdRh9P8%Ydmv*8F3cR-;&tN`jD`69q;vW8Xg!oK)-w(kCN+e2+_&;oH+&CEE3>>{ zsN&sH^S8aCcgCJj_p=RhEnG!i<{CKMHlNlT-GuMI?mB9f2D6SPvh3$B zlCB$ojtio3e4shL8R^e*KMRBs`tzfTU{jZh7v@M} z+cbH!+GmYd&44(b~g#u|mg3m>)w{x@=@+YRlQDbXS zrg<3*O1`I)4*#Z760z*JRgCkuj>1_c;ke>i6PexXj$gbl;x9Q{(x5jI_uh}hU-e(v zxh@Sgt}!G&wE$&m8yKr|5|4*Ph~jr-a_y-@g)vS)$yEmvmOZ*jmTny)+`7LSc6YXs zt?zbGVbdp~f1q5nCN~&fI4BAM?td8*lSpPO&qne@ijT|M3v&!_q2AmiFdlc2;Fc@! z(D?zt`zFzn*9kCU=Nt$$*@0nU6{4ECQ$ac5EFF3HB&gi@Yc0Jc3<9Q?!*y|I^fuT5 zW9br6{en?A=H@!6@RtGC&3??w76gwfy)fBdLI`--h7id#%?JT?uWX zH!lnHy|0ti!_MF_{beZsd^!zt4<&qZJn*JlP|^Gb%=;aTThH7;$AA+s_~ABQTbxN^ z>>S8&R}Eq5u;m!nc^51o%xUm*UrsJ&R6p%H-s{7ozN|p0MWeNc<*K0rQI=P!~@T4K@?Uyoywo+0N&F z6*S}QrZSMe#E3$bnGn+3Po9@uqcWYZNZyP$4BQETo^lEP^AAg`PL+pb^=|TRzCG#I z38Fz`^{~iX2IdKm=*dqCROVc@=vKH5m-A^MnveX=oVS<2)4Pmv#%D$E&K-nNuIFgc zDhFtM_m+mH*P!pVde9ympf`SBf}AJoFuFqww@tHV9N~LWcX}XX?3NXrSYO1iQv*L2 z%o6t5UxKAe&JpjPm!PsI2g=K;=@N%j@VFa*m#*C){tn}KlaWPmwJeBU6t}_df5VA( z#UNS5ykD;t3~=)o4U*)Yg>-IY8l;&HB?_^@^tN#bd8D0#L%XJ7@46uLU`%cBsW_J7 z(t*l>aiXu=W$BWTX0ol{9x|4xfnTN!O7efG=P`kv)fN*z27V-Ci>F}nR2z(bB8MAG zPtd1SKrLY|Xuaw~&DLgoJbw!0eD;LwC14f&wuvippNoF-PswnX#dzxTL&i{xV@?l( zd}B7;Fmu5#PGd;!ZFZO5GeG2*q@&{7C!!JRhuK*$A1_8vfezQ9!o@q5uwq^tj0(^u zgJ*q7j5jJkHgC{mRo%Ii1n`Wu<}hZUf-WZ_stwbWa34z zv1uC28>l3E&!%DfpMTcHZc6lu(=Bj58_RVlKOyz!edxFGhJ2SrBfZY@-aGFVlrHpR z?62N)DrE(E>z7wUI5=K8(99hmL}==x93$UjH*;Gl(?mb@HdEuM_nTEJmYZC6fI;3eLR`K)a!f_5!j9yv#jw6<}I z?zy-}$piVE+BeONz1F3W*zF-A z9n*vjq2u9D$sAPNV+rby&Jd-_P(k16KkBWd4?mx%VB7Nmw6a)>=Bv)b!})o|ZXrFm z)GrCr=LCR;kuhWG^WfrGFMO(!2PgHbiOZYmd`(d+UAy-iyT1&OWn#LF<=G5B*M7lA zoVdUo5g1Sr#&VO)o4GraHt^$tWNJXClo;wBxK4J7o##C}jfBMPR2)?tMSmT4q|V$3 zP(3-K)X}dUo!jl;SA!lne%pdxUjxA8zk5WvI0=f~<{&?HA7?h9hYOs{xK^W!!tF^3 zbo0XHFl|i)ofKQZ@rhbOO=AEa)>X%&1z9jnaz6FWM`E~SKSci2W~{?45?wtS9nzJ_ zzp4yykJN?LrBT+ZGzz7^MdNV!5^#4&<(AzGMvLuRK%#UN#7tJ?bECSf9)8}#GQJaW z_WU1Yd%|nF$KOT>SJC1dTd%^95uMO>`98igI0f^!7m@RUdAKrjBaGg366=q}!H|`C z@LX*e|Fw5K)}8c*;pf-E*sD3<+t5MpI{k+YI?h-zzme>{eVIRZe>Bf^TMO5|i{MR) z9PhF>5)6N+lO2-%baP)czU`fBm3?U}aK<_0)iy0MWLg2PS{}mgosuZ)WkTL=Q-?gQ zZ*=Zy3DI-YVdxz<3~Jqf;O7NfVOr*KdVFOF)@=HYlShfTsvnH?8*L6XUym{VuDGx~ zSQ?XmYT~bFLj;EicbpMu3ohY$5YqRI*cjwu-v(oBEq^C6-uMpbl=-Omz?8jiLS+w4 z$Av{%80RF${zr3gyx4@ED`0Od8Q%M?%jz^7Z37@Y+iot#Z~UK zA?rn1t$|C!N8wbNG;r2M=9zSa>Gmi2cVDA0O(aXsJ{0HQxbNd{t&D}opJSLqxDKbr z?;&ogOX%+SIfC@WQ>1pUs-T#fOZ{6tSa$y-X0DFK+Y3fR*E>UuofpKomStF@Yz5Dg z4#97;<@n{uI$S!*mi3~U6Uc1{KAm(I6E~!?tXwb*-5@Eb&(s&ZMCzb6E)(WjMu7Lc z`P>Hc(@=1Jji(bq-#oC{%4dkpp&X27XWk~F)dhS(fE zg%@IOpn7;GIX`h5%<^5$=KC_tPjiQCIv$Jhr##U2Vi$8*TEM?24Y*){`5*iW>36Ng zG))wQ)f(ermWvp_#>awqq)cELb4#Sb%-^_H5jr3LN8PTEroOgnfSA1t{F z_ou%?E%zu?36nyF&QYjtk;>fUkBNGEkoA-$QoO9#UkqmqUS--v9K-H6&&!vPR1ys< zb2RX>bRbbGF$DeDN8#$<)6o3Mh#GC(M;)7dU{h^2E=kFU+C5hwcEKF-K>08xq@=@J z*Cd?!Sp&(_STr6YqFPf~e$jF^)x0eaBbH0@&t$5&LyK?XfmIV>;_+d^)2Th=XQeml zY;0!FMkT)74AJGk%jkRjDeXHqkB`2i1CrfwxURgMl&kI`8>YFz=h6e{mpzMdvk6!j zc>?$Sp2DwuYKLwwZ$lsd5^i7E&P`}bz`(kj)Ym@%I&*D=YK>-)G+=kZ;lb4Ria2<@ zF60tIQpn}*0Q_42lI+%%Vmksx^XqR#Qt`n!%ob5{>{J0JP$YI9NQ$CEgeg#9aEabD8OqM^R=TiRz}~T1u>MCBe{^#L z)gGZIWSpGEhlr-o*l!Q-gtRboNyg>}|1cUE!y)J51~5JhR=a{hYz9O(H^30k7lnO`FU zt`CVPL+4xK*uDgaw^zlXdOJiX0$uRe@nZVKu@sX}cF{|=Cq?!j$}nFvfO9&=GFP4y z7A<#RS>!cXs&j#E`r<`WpES|Um%p+zf0%F}dN`kdC5aBdP!9H0+aYD{At*m(#67UR zhR4&)F`d;uEwj2wfiQU~vmhM2!FY>ZfYw0m1nz8UO$3>a+YOn{KB1mGzmJC;<5aVGIp4Zr0yA} zV3M*Ajzzx|E$;9m)=mCgbB+njdKQ6d>(byE{}>H}pAyHfPiR^CX)@{TY-(=4jgv3( z!1StBnA>qf#4?ez>aqo#l5MA&5ogF+$yY=tI~4NFLLny6PFUH&9C97^!GEosaOSTq z+2{ENzMO5vVzm;i_q`@k`foS2jqHahmrVJex2i>O=D+1p9DS0V$iKbZ=3i#lle@KCyA;~a>VQX$Pw(hw8kNe+5Air@@~$-TPxI4&NO zf1DF#oSKEB4IV>G-(Q#<`H*DYy-voB4x^85I$-$@UuX)L&Gy4cgY$>+7+4HKtf4=Q z8RLfy(f)Yn$!l6^Cxfd-zT_5!JY)_7Bfe};0Bsky=SOX?V-CwGa)0V(GPCNv_1X7} z1@8+JF;#yuESf(^u0)t(_rq849sMy#N`a(ybij)bTKv2f%p0lojxbOiv&{a0tHN!Z z(zhAquQqY|dfVW@`z|_n^hp@r^OVg1K5#Q7V`#`i<|E4R#p9mGxw#VeN&4r}!n%Pc z%n8;-N}ZDN#I!m8^C{!F-8?po8)v=HH=E9Rnh&{mMq$ULCYS_VBc4|`r;k5iPaDiC%mSGmG_{3gaB_RG~hh9U!2pYIC}0c z%UYEw@`HM7aAM&evi;k8nt0?QEfZ^@%3rtRn#U6S`Jxm|`g0A!MNv51`3Bv7;w|0s z-kh|YO@a83J2Z8zAGA9t;Z={(cpzFt+J@U;PyYy7p^!|59r+7-wklLnt}M5Z8*o;Df-7J(CB|HiX)>Zc}hsd)qj`i+Mzj$Ys~s6+y$ zFT=wb;mo;x8GqHKaAB#9@cnNTD%r6d#MA@u=~T6-Y*s7hyX^%G2_aw*ZVB_RJwlnT zcIsU)O{6~NEAIRkMpVRRuzuuHygywGhc=jD`*Lwu>Nb+iDb9n^k0|{8+XqVuMcBB? zg#UFS7dlj+w_qqRmHRX!StZAday2VM-4oe@&#^ApM8@lRO}u?FRp4H%az#`^@- zqU8oVII%kp4Q_g|Y^nvTH9hgxjh_Iw2EpxT`m6wI0edz(?H`#-@9ld8 zn~SrVQ_cdEm5Sj^_z>E6n1>O;)1gOw4A zEof~ajGvAX`7*p~NXSfVYP1)vP)^6dKs90bmQc2zW-eUabDFUm4`7XxA0~}D1{?h^ z;=C!A(Dk>2G7pH~=pPypa-3-#8yM{#1z70FPyV-YK zo1A^uPh+<|W&F$-5VI^LJ^$S&V@+jYLfUn5c|$dpvG??u!n*O?hCId~@V=RI}y>$V^=C7r7{eK8^{lMANSLl`YbHq*54u4b+fNa$bI&SYh zc(A0BhF@QVGDl0%e^vzR?M&y_f5^mqrH$0Ute$HtHiO4U%rM0kK<8PT=%q&_zO$*c zB8P6$Yi>qR+cy<*q?g;{s~j8BS(@kHf}$Y-S;~ z7WP+X;Jw>VxTvp;v5)bC>~B^?U*;9dyM<7|*sf`uK?a9%L(=dd6cE{upGQ|u^71Oo&gMXSZcH3Fp zo#%yzV+S#QN-Z_3y$aW@ELeX{66fk@l+H?+#YxUy0_$(}kUK-<`KSd>9PIVS2Mupw z{*;*_=$epJ)3CT#X-%yKIV+CrK#TW_`S>qj=UWte!9=# z^+A2eaMPrh&u7vtSxd=};a@oW_pjJY*q($+&tiO%Kiv7Ez#ooY0EwG+!N62sc896O zwt!sBITnK^NlECm$pHeDx8k25P2&E?A7)Bk#rX|Sxylv8QS0YsJTuyi^*Fzj$}NAz zxJ?mR=-&dJPJQHEjUoE+hO}8W4xC&pxwJ%Yk#$G|t>}G0KL#5^iqZaPVxyFWR2=4SR1V(qmbNtiS)6hc}PUWS&4Jy1llV{#6<#L{2q_1>uZC zye!8&LefHHa54M|c~d$mRz|SwRDiwd>!IDNnyWwh52_}9qr=b7hR&*u*y1IL-UZ_* z9bNz%)Fe|6sU_GE{AU2gz#nqPpv?tRB9NgSw6M zRpV-s)E3LRgj?ffP>1X-)!g(EC$W20HEbNNDl(2UVeZ&@_@99@9DgvH?d#bu+QxV@ z&$$Z!^D|%s%lT;qhf=SiA*gvKlFfS7qnhJ3w3u84b8_m)VVQX_=gw0aZ)ycEhpRzH zLnazd&4!{;cj@KLhw;pcg>;W}Ocu0W zzrxp&dbDZSU0SEw3!g&uL0N7CUvae&jCH@^8poNmAr{!Z-Gc47ae)(4htjx>r{H{` zB!6hqDm=6$4sP2{LND8Y=$r2X6J6eufk`XCCHWzHHeJBQ9peO_m!pM;`3giOLY&XF zxGqw8I}AdrvWQM@KI!O|;N6-f(CAA%)hhprgX@=)F>9)b$&6gAXqYXk$(aoUD2cbC zP7>{925@bGDO{T@4LUZm^up5&#${ZDbeR~Eppi9wf zzVOKmJj?PB-K$GMet`^{vKg@A=!qu0dy||t+YMErpw?y_l-vayY%EJ%U*3w^x{8|Omh53Tt|cEG`)cs!QAxfpw}LYi zbl~eE1H6CZ6r4?c%j%ZP@M(Ds%Yoj<%3Z5q@?KeJODu-|72eQg8HzdGwYYxW9QtAG zLM*EIh+D?w(3>+BcXarIM!^7yRZQm!*OivWbzDcDDhO5oj9O8WSrVAO?!xKICSk4D zV)8a&FZ@28k8Lau5XHRlVxA#T)T&7J;`hN2jS}L)@?PVE)~CM$>q^r~x zW9fz{TsAlrvSvC$^1>|gP*0qXxb>KNyo!U=u2#1AFPb_x#t6EPHKCwqEI(&MAzEx@ z`Ci5^b9F<>fE&`@QD4aK@=$)x(YNTj@eOfrD`hh{CE>{valS8ZA9eLP&DCt2L_Mn) z5Q8vRG`wQTTGYkHV0XBB;LCG)D-l+QbfKknhb5*tRcns6@9(R7LPr(fF8Tm zD3vh}a^&{IA-4$b{@_>g?yw(qirYl?_-eAUa1v!L@8fuu zzg!dZ4R(tE#_h(zsAF^*o(DzK-G}T&-z$trY;7IrR*j>>4llO;==Kgr)msY3)>l#M zfM}>H-2l&`a!GU7RxmOWXFf&=VQ!nD@a^y&Ji2)U|Io;kH0SikX_%^2-W^(Q%Z#4K#okk1Ws-GstW4%iR6MM`YMoR?EB& z1>LbBI5)$X?S+hB{ty*DcWaPv*{Fkf)<)nBPr`c_27p@h796l!V(lk4nz75n$&V>R z`Qzs9^t4YecT8u18?~5uahtkHh{zf5${WH2F-jlHBf5{eZ*6pJIav~-!1o<{3`Q=} z)Z_YI5FcnE@9R2HRph|uTAml(yPXHU>m;z%OoXG4?qxe3l$ZS%AKU=)JsmxwCsvTzJ>1#g22-Ex$CQQ|YNR++@RU(v4#ZB8Y&*-c&XpmqKJHk&a0gsc z8O^7Oba1W14GdaS12$ibL4Md9$eAm~zY*%G>UMXQ=`j-J-FSfJWxFtft0PX*I*<~7 zoqMU42U8EHLIv9!x@va>(pSMEEi6Q4e8kR=nUJrNjQ$Uc>8W{&ympcXjFwS?XW82iE6q|?M7cS$@wqDw6k$|r@zvc$pyCJZ2GTv8jr)yVh5&Fs% zmWkytCV2$W&uoUEwm|x@%a4wUazdLq=FFAVe7o44d!%otGG+C$7!7YY5TQ}Fj`8(eU&7p||aMoXOy zV8h>Id!*ip2AYPT+ly`}fAgG5pG=04@=ct1OBBn1X)@kW5)ZK1=`=4*Y#@rX_Wc0( zDr7-()^btq-UO!2S`6I}M}o;ODK;^`gCB0QT>pMOR&U3G+{{37VNS3SWWF z%vge-%I@QiJCo_v({Xs=u>~zXBEidFX7>U?3nu!mVtnzRv zU<$52sW@Y4JRQBMimq0?K(1suVB?sx^iA*}+0%zaJlL1*l(A%t+2_&(2A?@-_X6`5 z(TnSfutDx4XF?j-`d7S+35kkFk<@%HL4&iW@j%ixl(oT<2TJF5$<^VDk8AI?KgOr%OxTlHPxd z$i%C+;m>z7VfmDmWT}7<3KEbt%?$%pgW&glANUX>Cy3gdz`^PV2JpFpoV^^s-Q*5@ zU98EA9tM_9t2_sFs7JhAbc72}{V;S~CUc>=@m7P?IHF%x(7Y#smnIKVg~~*b_Ah6> zm?t9HJIU}S3~+_(1>!kwGygDaE4_X78s(h-vYgvijC@`pT9+InI%8phy0J&_>$Q2J zGp!FqdAlTe&+#Jo?9xsa)j6g^gru8~xb#D6;D4Zn-OE&j z)hy@URR4}xwGM**fg>>L`+IKt);zRzx1PTJXF}8xDaS!`*$~(A3%}kaLB8$CuU#Eam1-vP_1Dm%-nwnBL~>7E|$~H zT{{yqk~D>}fm6t<$@{tQ%bY}WJ~=~_Q8b=3xW>3}KUk(EPyD)Wv$HRUu2#8CcJO&@ zhgA=E)H#gp(CR_)WPO+<_mI9*V7;`%%kgvL2x0GPZ|qt28W-PMPZ=j$dSrPl=swZI ztEU-vsWX=CIzLbDuV@4{gO9*D58(@r|FV|u+l>czy`wH3cj$g+asHo9C5dd>jDFAF zbBRN5lDlzikIz5}l-;;1+I1yZka{4;iz51|;qWY6@VgVFW_^KSIwt676OT6{-oeAX zUYK>^4nE5c#HUH?Fw8EFQ*O8kah(RRo-M~4`fMjA*kzXn?GML=m(Rh@KhGH3E{PKL zGx+x9b$F5t6%Od;Qw6UYRQ^0(q~`OMd{|R}BCi|NFIG}yc~~4*KDoLX^P$Zz?=s_ZrtvG4#*$SUk{v z46WWcVQcDCGWTCRM(%Vcb7D+EN^CnNRsZ3qHw_^EvKc<@yoN0?>u~Fh7V__jl#t;* z4feKurC0y>5@*L-^dk+Y&niFDPgf@howCb7&FMK2yr@BP9rDJ;{W@i&b$Juw0B4{Nv|#@=NI{bvhim8r)q5g;KjPujuV~Z`Y-0Tk6ZLEd#1LJ6%>|R!A8F*Y zCh+byA{+ZoGiS><_}p^_>Rx;&Cy$MXc(X@DKc)~9=bV9g!I|hFmPAW@yJ_FCCgR~{ z0Z)@kaMMs3KF`xe_#RS(iX(U6OAiTBp~U9aC-it5=aHC`qXr}Ag<#o@(P-6roxUr0 zgnJsq`6Ca~tkvci5~ocpPupxmOD^riP|I}kNK%|$_|n00g#p;Qs|2#2uR&Y!)ogcM zH8FLQfe*U9BDF)lSn_NDw%3)wt%ncsfTJnon@j|flT@m>_bBsRTa;#R4k3XFIjEnh zk9(sks6}5Kpvh$LNZpHmPO)%ipD)awpND57)Pz?o_i@ZopT0bKnN0V54BPXqXz*Wk z_8fdl49iTIA8oVn?4zU*%CZHsHg6Hm&yU1^*{#HT^8WX%m$G(ajtT%6QaQ8G4?IHu_;_5AA`#c%P@%(>xG1{U0hrCQIhZ%{vbf23f%L$l5U%@;sc*pGQu``c?^cF?_)J0g<#U)0ulIzR zPne(NKr*}?)`?42zQ9=H-xy!UnBVq%wxc%}^gl^rs$&5hofm_W-NhL4T#hj#?Q~xG zb_~-}pf^jjVY}^W;Cx>qEz=Q(8&)xA-#>V(CQb8Fbb)zSsOpGV>!AGtNq-tEQW+yI z+_Rd3-4B*x)_?^DEZ2p?N@e6+cd^ei502)?pjO!hoMj|VJ%^6s_g1kzR!~n(lLADu zjB_FPb{2ObwjHuYbW`ELeCq}M^>CSiQTxIe!Z25pou+D2?@kCR24j3D9FQz~_ODQ+rcCFP8xFl_xfI56a;wTI?I`cOuJUzdD~ zY`y6WYFUNEdfOzyaG5tO`kICF-YW2^cZR}nUG2cdq=-kbEGMItv zOpl`Crass+WeoX*M=)WEnXqnZ4DmG@jazg~snR3~TyB34OCA5h;wlF74=te?yHiPC zn3j;WvXu1II>V|&Ycvh%AqyM&Sl^bQpW`R;BTyVh^mlTzRmOrvZPAejhO4NS)6394I`&FZ-s>E5;>H9(KQ8vWZt;S>03t-ll+Yq*r?V_&#kHwO6Iiusd zVTx@xeYU|L3ddXVZ(eEOiOC_LU9Q5r|C8izX)hzH!(|zBxB?djIWsn)3HR;U2aR5g zzig{xIU7A3v&IR{?;6n&bUCk{wgl}D^pI(*pQ6F7rFeUh9UO1_i*gp($UQfttIBus z*JFd3*Cq?EiCW0DW5U++6-jv3m;H|O&bsnM$3YF$yVlcVr&$)?Et0;vq%J(QOn?fRY!t;8v*%_x8pck;v>Ol6 zSSEo;Z?J*Ss|vyNt{hIO{)qf;eelRCL2KshDKRaEox6u|OVkR{)pmf+v^_&M5*=Yx zggP}@V+F3i-hflnLi{v$0-XIQhe}b$h!^W|E`OWEOK*0=`|}us^;*;4T6t#$?dBxzdkkD2OKl=6X+96lMJnv+p=P&xzdyv=L z_=r4}*on=zlZX@BIi&9XLq{gg!hnCfV8mb`wcMJ7J~b)qXHt*!^)5_sYTqKlJzkozCu%t50Fog|YZR+Zme*r_=kJI!NhG zYZ^b_2du|WA>J)wr2Ix9oo{p$2Rg1nbIVs)8y|=#m4X9+C+;e*@1w4?rttw1Ejz*8}b znjDgVIWt_*Wl0X%WM6}Fd?6^dx`5e7MY{XF4le5mp}le6MX$>Y;Iq0M+ZS%5YE#3> ztGo~rdqslDU6%{lGE1P=Wj@C1oT4_tniV%Qd%$hQ0<6C1iOE*QpmI}>JM!TsUvQY+ zefzYykk~rrhN!KW|GR`NNGQV-AJ4&g*E!%j=Oj)zm;~qD(?Bz0kiR!=Bs+&zL$|LF z-LfzdG@N#`KEfU$Zea#_amtea9VfvJ|KpgPWE|A|BC?hl+rS#&YQvCAjCyM1A5k2}O z&>t7u@LN|psX8tRR;=gtI%O(j=jy`4om$+MW8JhrErNOuli^C;Z|uiBF7iH|{OKPDGy$!R+L`7qq{RvZ*Q?t=Q|H_0h^U;6I(I?>HvTA;OaK*Y^t zezyyqM88EH;w2-POXL{K>hQ$BPL~!s#u9@AvZysJgM=0g5mdfEC$BVg$b&=mc;+>7?a0VYT@$j%DQug!(+^oM$tPUOpU*|>| zGK{g3Z%Lv14;}7xnHe}w2%^i@A0dbA%Qt=Oct0MwNG=t<>U@ZjEW zY*?R(mqMhtEFlqT<}E6FMpiVh72#;*DOlWj6Qt+Qfur^bX<`(4qDScBW%IhawZ(EZKa0mG^kG}n-^S4z=btIxNCd|+1Ibcjo4?4KW~Jx{A~a= zG7w`uXGuYN-D%vpY&HEHdJV#y1pJ&k9rx{iNupoKGDlJjKD>1vH%2{yRrbeOD{ASO5tyPAg&S{Dar~U%(Ewn_Dfud-UVB7O{bX3E=z?uc0=H!Ts7I4GZP)} z`k?WNQfjpAGkrVC079PVvMiW}p!W3z%UE8AwR(3*^{wwDaP%3x8&uD7Qf}b1+!A;( zWkK`J8G6%Jm$@cuSyw_}3e2^1&++bx1^U{sV}c@MfochRThw6fBpsNNuf{EVAWJ4? z$PubiMmXgcn6cUi4`zB}C7+5NFUN4cjK$G4+Zmr8nNDpJO3`KU1=imQLZ8R~!M(3r z=wF{=K16mYYOJ2h(ZI#T=yW9}e@Hjx(OL?8Z#t+VcPc#ctBnRax;6- z&?OdE9XLmVIwfJXn=%QKpNZ%F^T?=!LxiH&uSkvSCMb9&i*0MeVNtpY{^~CvZoX55|oohcU zcYNVbteb>gSS=bZdy=^hN+7E82~6JBg{ophaNz=Vx<0FsznR$uL%yAcuM6a1QK~pb znn;rJx?l*+ZvutKi=h0*Vbc90lFT17luLEsQUARi7TXN}Ummq$aHR~_E8~L~mP7)6 zYh!(**DPnnm`ESYFm0Lylp2)dxi=%ZSWRP=r@hZya!0`>(T23EDMC$}7=*4qh_`*s z!AIH*_ixeVUNLU2$*Mxadbb$SF&dP*#zMioBly>Qujq?v2DG;Xl58)8=WZv6DRcd; z=qyE0%@BDsUEpsvXmSHU_il&I`-vsT1#s z?;U&c@6jG2WcAK@(EA@Y<<%z}o?}@RPYq|!5%H;6=`q#u}oH{Q*Sy5;z@Fgl`A4vzF221WZ zz}N4A+G@z2fVQ3_Aa8 z97$(q|&maPly=``@C1yZ6q; z_|A8X6*-2xzRn9BZfwJ4g;!ybjS<=8sR^OtarA-aQ#$;n9<6w^Ryf;HCQ9>n!VTVm zqCl175bphnW?qpcez!8v?C7BRxTakqK1T}bt~BHNrb@gd?ne&yupaGPPY6yP5X}p1 z$MPhRXxy%3xRN*qObYu!u__&!`ybQ)hA}g_dJLLZ)zgm5AUvZ~FS-+PmdL%*6Mftg zKt^s2ChFB;(DA;uy z@BZyPjvs%5yTtzggN~lS%{Bq-J6i>Zuf>7y-z#8x&Y4O?Oar~iJ=CSnkt}y8q@&i1 zL!}yJWL_%T@aQXvIaWl*1^yQ8iBYx)Yi{J*9$tj0Dq~PH!vcTTD$t(paj@ZuuZTA* z$HF01yRVQ7{ay9FN7LVv!&e1)=FV9%idhh>_5Pt6!A3Y#nhN z8l!|MkDPIiyAs{FN=q<*_kkbfG7h2>CR_A=y3QQQE3p4%2>zZtT)5$s2@4NTY=C{0dUV0a80I8X7Zp6Y z4Au{S!?TtM)|rw=cw5IbsfZG;uzz&JwUqGoAEQ>!bJki&Sk%EVQyL-B%jk{W_Q@+KaAsYq4|H9~^Y}g#G@H7}I$c z(o0O>INP=QpNasp{|4!pAX!eUUK6wPZqbU>lYlC}X1Pc;%sFO(YEz$z4!9IR?$E99 z>AX8p-MCs8JOVmstgi&ZS_|Y=?|`Fc3i!5N#AKxI_#Xza@9X6mdXnBk|EwC(VE2=;2M5e^n#B*!{K`SEtu-@3esigdYU+L7zvzz8vrsvuB;=Jyh904qeZDBF{(q!j8xUvS9OX zOiGMn?x_%Pbbbj3)$cJ*Ib$C?2EqIR1+=?%3?+kYu_L8}te$leLabUSR9)oVf=l^W zt?T6>U+r;4)Kk<}*aLT3JLyn)F|;i;E1$V#98r(U!T_5d za3I$rwBr$$yq+s8i?#-nmBr+LecIEXRpE%{4!mdmoiqoVpaQprzyn3nKF)-gX=$>1 zfd^H)xgL_f>2voxQ*lnpD^kMP+UHtANbPzj*mQOu|A&GouwR++anA6&%~K%##TRt{ z_L@c<9nP~E1?z+V2OBob!|#rgLOWw|7OX#rPY-rcSHCFoHDL`*o1S4|vAqDVKA!|a z{!5gsnuaEi4`H{T68ubm37NCSV7YNR2{x#wZEfYE-S=mp;d%ip(-||+CX`sMIY-7V zQ^1`|#?qGYlEUp%PP9+cA2XsRa2kiiIHlD}*n2`C)1eM_208I<+-K4C1FBqDBxfKng_@kZHJ5q z6O{W!?8 zp9IAt{}ST`U6@|78D>4WgvU1qihd2qa(O#=jGyQTHUl@nQPUpnet9QJ&p!gZz>WzcAbHWf@Ji!IR!J9T%=3h zIMHuWN3iqIYMlOTCK$(@BEL*Rh%4Oyf2Xu#(y5Ci{)wcJcB8r?Q%#%}F>Y*On>KgZ z_7hc-xdx&4wZQzW9~mkoOJ9};VP}yzw(OgR9bqX!+J^+^ek)(>@2_$yZ-UQ+}+uMMXPbV&Vm@Rk4&d1R(sW zr(F7FHv8YrFP3;nGc*Eecn@Ix_8(CCs*!jEH1d^ijA2z?utGfm zqBn=Zp+=srick`~L(aqMeih-$^!2z(!;yR5U<@uxbZKi-09eJu;gs=Pq2^UBx){vD zKAi}9-d6=CHY-8df+G5leHU_Mv|xfvGn>in;tVXBiFk7yq*gt}DoJf1N2O8EDuK2d zIf9wA3R=eZ)7H%UtlwC}opss0n-ihM7`9L&X{OTpl|sFZOJXVI~iDX`~>l5mRU z7MDr4vAjMd=f@N<-tlSjU!MZi+I9)+`@h1;CdM)RdjYquDu?Z-2I#6GaUgM*xj>I* zk%pook+su)IM{E-JU&ZsM2?&=ur3R9cTC3a7h}$MW* zH2$Dw`ET)V;A;Nl;)l$SITTwU2W!{I(FPY4T#!-(-AaMv5N?JlIdhDjB!+>v_CPkv zvYY9NQ2F#UZqpvgg?;up`*DmZVo`a)^8XtL{!>Jh~XiTILyl3-` zw?DeT^!9ix==?z^{mddN)(hyaHJ2+oC_4+5l!$gRpVocZ%cy0&pSO-Eh3k=Lc;lK| z==+d48oph;>#c29|R1M5(Q^u6>6(Y4^8Dv%aDfWG6 z1`{)Rh`k#o5{Ay9kD=vz4RT-MXw=lhA?v)O|0gRA)8HZ7cYejW{a;*aYuw^tZH zW6sv4Qe27qL6l!$1#TW$@WqgZ!WaRgB)}OEyjrgaIw#?0)qLK!g-BohF_wK9q?=zQKtSYV^a@X>?P`|z>cdyuP%I0FWp0z1 z-;HsKR50Utn{stI)0m^dhGmnKaN>#v*gQIidDj^uGerlE6fVMK=5Rc4bqkJN7b)5k zI|6x=$51A_7=wF%S^TugAPcWnvRP6PG(9xKdww5?#=|F6ahE-$zLVtw%=Ez9_&d(K zJCA(avjv|)H;dol60^82}6ETZ% zn6^U`oKi2cJ@g}bcwQyFZf1lb3mWM?mg7zv?hWTFwD8LCP{uBO&CcW;8Z+-4@fcVL zBg86T+V+1W)XSVxe(6s(F}6>(Cv*eMUEcvq(LT%PTKDEPFF)%r6jqce*gy0 zDS_eSR{G_&GfXK6Vdru#T8ApYOq<{4UuNzm*7;M3eAf|_>K!dSYhDXaYhIy3lp*J7 z>jixWf*4;Q2WwoNsFzMSyIUN=l{dyho=O5pi0^~blVrf=_5!R_=_ThIRUm)(e*8F$ zG9RvjaPZ$A7#w&{W4c~q_xuMI>htE~tV6rO`|p5gLoPj{oQh)+__^q5Bt=W}WPJ69hC}mIuG*%_b?iJILre^6<$?o#oqJpyx?VxLCRi zUU(M(DxQFZ+;BRD1OO(bLU2P1IcHqKx;&G}$EPnWBE=;@&i?}q5ZeP60y7y0|0c*T zJ5PS>nuvBc&N5EmK~%ls3TG7WV)I{hlJ;!|yxihTA!ip!f8jzF7f!=MHP(krQo{F2 ztkbou6OymilG4-}$Y}HeXcE|I&`#HdTmjN8FHGH$iDQ~R!LQ~DyfbbLpy2?G*J=(gemRrT_Ls+a(J*?%#N)#JE> zcO<#le{|?hT!g;j>)_#?V%&G`BWa2l3j_IY$!WbC%&D@74%PV!zfW!MTujQ+A#v4|H;n9O=ys4ZXQ(gyZ^*OlU zl@u*FQAB^83nEdw592`fY%~r1MSPBq1e>=Rz`6I(yn$@~>SLby?j<<|iEzrFJ&QT9 zNKCuRx%sFA9~WLHUmBH&Pe%~{vv)Dtxugm?T`E{rqa!?gGZdqrC17oS4s5bt1iZ;@ zdV6Xlj4?a_9&QrtkyJ6gk0!Lx+{e98?Z!PHl#em8-Nq|msR{6ZU(NM6) zOgO3{&loC|usl?bKf-QX^qQ$yv~S{RH*yX9{)Ba@=E`V)*Ggi`&|+ zL6=UCX5Hp~vSNakFztjkmfECavxha;RM3k~+uxyau_q3NAvhuG5(pE^&}hbX@=tdX zT)8<96(ZxvlP)`Mj*_xy)apk1-;GQ<{FDveY+OKhJD;a9dI@Onz7wsw>v=&+LgXke z#(mhB4sA8$NOY*- zYPV{($Ed=!$2P#j5KV|`3;>w}V@S7k1%}=~#9x*3!2`$K$gv;3%xfJ8UvL6B{A8lw z$}dX!+h{t|4_e*i<29zeKN2pAXN20KMP>iuCUyy-RoSyMABKgV#4~_c^DAkg;pJt!A&fT?@sI?H|rOPJ{fPr<;HT@Zkj79zIhK@ zXS46c;$GHQDTMdiQegbh1$cAEeX}dWlkp@!4vsL*Qsj_kEL2b9QBDthKFNt1TsvVD?s`0%`xc)EgL7rM6I%J?OY=G?W1Uz>v0%|1 zrz6l*jT=fmAZsK%_CJ zowq)(jAPuI&_}NVw{kr+BR3nH#`DbIc^0Im?|_yzJ(d;hCRzrvLYj}Qug2JMF z&QksZq>9%k=}&>;P21JdugM&8JTLhtHz(C z?ineR+P)?6)%oPZ6BE`=bfuDp%PaDFZE)dX_V2nBM&y!zK~R}p#j+w7s8)SI$Nc8# z_5^E~$Jq31WyZnqlaED%Ie#FX?N8ia=z>-3Ow3iyhBNh#$$+^hPD%|bpIIMITB3f! z>Ec4Z1$a83a}G`K-C{ct32sa23m7G}7r5Ckp_#Ejwuy@iaf_-URHqfRw|EevZ`ru3 zdM;YpjG@(+R#W+fKN;uDl-Ar_gU1XXQSFI7qG$GY3MnN??y4| zIOl(T%F|#i?u#0EcBraeLT0?(jR$n!!)?VmSX}1_TS`8X*A?O{>vTi(vBv;2#OFXy z^a++@QNd3RIk=P0VXlDN6!n8JVO0km$GmUjAN&Q2hB(Z1{z%g0mJ1HvR=Bs|k;PK% zq6!^O@M+u!suX1>%-uShS4x~FxL^(MviabX|a2;R^9ce%tzwud>6#MsRH1sr^v zD0;czGMyGb5f0DurN{c+;L=!67&$+L)*fB}4%>EN@253*bIDccyRQkGxBo)>>80%c z=M81C*D6+U7h!;evVBYtiqCuj<8`$w3iq7_2f1SMvEGCAun#g{auSSjKLr^YCcB3T z*vHP#03di9tP=Ley-JpK<`NZI|>I<~xQ2|LRk3`88(YW*HUc7O)fO%Fz z5yn-M5_?s6@9#sUwl-qhy8ED<*#xilr{LsLJM_MrMTYGZ7fwVHSTy1m{oAvfEFOB2 zmSlNQmC|bJesa1HAi2*&+TWN z&YSf8FB8t%aTR^@^dw#B6Hh*w2a;1?_rNNa`+WY|0C$ldx~SZ~qGjzaXoFMeZ>=V} z+?ar2m09p2*%hZ8PR63a@f^#*V?%uf+Y^pf~pzoa+d_5^7iLYobm=fWjjNS@*w5!KZCZ_mqp$h!MMxC63(ec z!{@nANb*xFSiAN<`FT1YFYSv0ml4-da*RN~ZhJ__1-&HMHM8K`rYe@Lmg7We%aCdpLhV=R2WYXYaH z?#7M#B0)OVAGXac$G<&FLeo7bbP__S->7ROE#(%hw3$M+Wkw6nRi2UUB^>K5><|pg zh6>HI5*Xigt?=3)iZlnQqHXjJqO(hayXw84eBFLnRM>V3O$U34*7uKOPOmig{FVbQ zlMtt`a+N_L(v41D7-Oy^-9VPOZ@{SSO%|=QFGJJ-ENdoXGkV!7)@gn#lJrG2tgjUxUmQhp4gQ8hWZHfoh1eJ!@Da$Q)GSS_=I!a;(1v z&MSlWhB>g~VJe=fdjXG%gV1o}2nezMD5{EUwrKY70^ci&uwa@m>6DRRcZZieWAD*l zSr74Z%TJvBw+Pf@)0j7BEGl)`5V3t`*fG77XsjH>$%~3$%a$wfI?0%SvH2yH+pa=f zU+2=X#|lW#qeo%TPRtvpYPNzV|))w{*wxnt^ZWL|*?sTmkZqpK zuXhy=KRXC}5B_7lp6{Yq&p#D9MoQcXSk3rBU$LsQMwFFtoal^G;r>aO!pLW4#FFuf zS8jTa_D#|v@0>t*G5I~ea)AwgXZc}pS`F2I^YMGgb+)5>N`BpLrYpB9!T7dR+_<(3 zj^sO|dxAc!RJ5viYMBK&osk%p^_lXyM`%e#6+RF@2T}&7q474GO=JbbAKPpqL<|uQ z#BU=852S>6D_J2U>MWY>URd$JcVoBrkv;Bq|%j7#W@m&n8kkJ#J&}t&(!~RfX?`SZ!m#5>Sqp<9FB7~{#pw*!>QG7=++%cUA zw^{=sJ~9Ym3M7Te@{zEkV>X0b7vnS9@6zNAub?MZi)^v}kLAhdlGz@GaLCS-n{_Cf z1Vr}2q#X^whfd`7FFA;d+y7(teFJdc=*~abTYwiG;syI9R@}~uCGfKT8LSsnP`_$` zTx+FNHOE{yZ%RTVVblO?vi2FL}Mg@qgVF zX!bbA`Vy)5{MK(?^-85k_f-!+!!8}J%^e|FJJ05p)}I5@vr&Yvs$h2|dEr9Fc3M(3 zjt1^Dqt4Z;T;8jn@cF$oq&$}9_Vk#u^Yb+MYM{br&J`1)(l_CM0f9Kfvzo6wU56dF z9^l>GQFwl741`9M@tVdxj8(A`2CDnWt#ii(OxTHcmnG0CQ7Xc7LwUTR;wg9)t%sWa zG}8Sok3YQO6Q=o9Q@7UZa;nr=ll1p1268`%5~xClf5U z`VhrCisaac6Xcx7bnMJuLZo#aVA{)zWRsd3e7|2tHpIC@fz?A2Z}pF~dc35p3C9On zuEHS=$EfW*S;kO0i$5}>=q=Sp>`X|f4&S%Y!|{*GKYM;5wh52$fz~(Jr4x8^9~)OO*ZEu=vc(J1H!VV$$7^u(hc6KKvyy*T>dW52O34nFE7(^st=waFAnT6X zp-xsmf5ZMP`{#_wn2XGbs2qjsv}<9KA7GBQD;mxp13Sk&p+&z=(ENpVpk1a5L!}GR zwu>=~{YHS*1_|!TwJM9?x*X=KG^MZo8<ZN9h~=>Gzvw>>J2_I2A9?)I_=RQH%@X23L#D(x0}gslkaCH8-568`Np>o@9K-gYM#%gv&BfmK$ zrlW$qxY5F^4rzi+r|+=2aTa>?ZKkqgf8EWoV+?NMF9JCGWqR;K{6! zs7o8^)gy^~$C>wZB4gOz(sH5ln=|ofPyu;W`7y`+MW{*dqtTP>xI-hC z5XBsU?O;}5^)74dNFAVGUfDzJp5<(2mWmG7*%^7|8!nln&fa^AV3Besn}t@3HXBT_ z*p|H?@@7WkxTYvNV7`_#g?Q1raaIuEw2|=_&cZU?GB8SXfGO9Eu=C@0a?(IbFud>- zpEsy7w{Q~VRVu?kXC^w1lV$gSAjV_|#`_tCX*=&?)bGvEvL*}z%s6meB#0jWHw9g% zche0U8mVE;L(E>yxDMH+WXa%ka2mTGZ71b|pYV`q$5oSm2RFgdK{>Egl;fUb6}#j9 zz*!%gNk_jQF28r14jgh~`2a#z>d%A^TiWT50C(mZSOyU~QP}=3kDiWxM(ix#g2j%v zSg`RJx_`e1a}^9=r1cLBOc&$(@-9Y-Rxr{RjjsYGgV4V+I86}_t-0UOtz!_f}j z)Wabh)HZ8lR>c^)V%0mEWN?e^QJ2vXj3Lr+NL9$QuP1-w|6=;4%Oq6V6y_`%Nh`cO z*q(a}_(Vtv4bi2d-C`^0s0lJc;T3P@>B%88e(S^cxdrg+4%@?ECFF*0 zf)7&?h{nFz^v|oERMue%`P?#3*t01J)`ga#wq6DqTHOM_SvIY!*OXHpf<0QHx%7v~w6ol8c*qpF& zK5XvafS+2uprW%3zbvz7`ON*a>`N}Kia83&vVAaeY9syo`~$H)R)TXf`{}cmL}HYN zkXhvb`_5btom@5(BG#~6!jD9BGXud&b}>{6voW>h3FXdYvJRRr9{zd_+jK+l`Va?5 z`&P@C#}~jNU^3^Vyoq(zGT@o18&T$~=rR9?Y?c*6TpI(?Z*&>GG43*1cfgAFX*8VbF~#bMl;r!ceL7Z>?T39gK5b$GL*;O#kuE7y0##GqK zN|C31YiB@4TL#JfcapjKE;4R)G)5;4fiG{7WgZ-$_Mr{i!K@Vu=2?Qpk2^Hw(QU}S z8foz|#RzPI9uwPz`=GQu(Bg375W4WlUH-29Xy5{dF+cPXqBw_fh>I9ABfSK#uYCwh z7cW6!-MNa#ogu_XNmdxVt3uN1hojruXjm*!Ofm}Px!UocsoIt@=3t$SZBmh#`cH>> zV}=R`A`|L`Qaal4A}$Y`4tKNnkjXu%aP3|W6(%evrsLj{`*QnGy?Yd% zx2t9Qhv(F7w|g8s z&uS+=7kK}FdT6PbLwE1y^!Z&OeY4*;g)aTz^R`Q?c>+U=JKobMO-=lY@C5vFXe^3VtX&W^#e;AAPEfqF2~(Rxpsj5&UH?V{H;)RY0qSaGalQ#QY@lSNuM;^st_{{{`r(ek zSR8fiI{A0h5+Ccj!=y7x+=Qr?_$j1?=3dsuk(+g(A&$UV?K=EAV+^FW>|lP+pXj+j zN3=phk6RUbfg~=!Krg&@6#nV_BCFFMqe{6EcWRL)m-}}nuC~(${$gN-)Yd@KYP1oO zw%Ngv@l)V${A-d}nT}d+UwDQ8`pBq>D}{++&a~YzhUJZyWBkV+BKvncoV(k?7-zTm z@N4FRj@Wf#bR(7JH^jlwq>84fAHn7o&0tnE8#>%wNlFLn$z&l)i!-bOP^d78yX2tv-)u<z}6Z~(ZaQ(Y1Q#O=${#hk-3XecZ4k-c_hhMI}NZoX9B*sev936Gf6?GgupG& z1m|^nFhxQNpITfeQ=(rIX%`U=Ix%*;_$lg{cmQN{55-bhsjycsq|KURnzGPfUXQ$InCB40WN%a5!ll_)RVuvuC)gE65I7 zlG9yEILj;)8qeM#3euXmWr{D$R2k@at|t8k-8A`g7_r%%50%R9 zEQ@sx|2^t~!LWxcD}4qEVs7CP@jAZYU^;WrJcFeyV=P^i1alME=Ug>}&b|JEDuugZ ziHEq*R#Z*XmgrX8$mxT4`D!@!`%Fdr`Y^inffzKky&)ZGn%s?nYpCS4A6NRFBFkA; zU}~>5ju>nw^{-@w{ohYen;=85X*L2y59U^wwV3sKPV=70E?{6jNFR=Egyr|9g3SF- zSZ=k9v^ykY&&QjjcsLgC@) zE|%TY;C@|Z?n(Bu^qRYkW(E8uZkOWlqIVCCy7iRyFHIv$XN*T3`?1Vl`JMbpnN5~1 zGGMdqqv*HOPgJbffzin|?A|Gc)@I$f?Y0?xnK+#r>3Wk9KPyP|rfKB#`Y7fG3ZN3E zUAW@dUNT;%42xcy;iD&~Xha(jqvRcotGA!bm^l)jBp85#RvImz=!o-w)l;#$3!JOc zCZY7@8}d|V3(C$?rnS0xL?l;F2JhU5=lv6f_Z1sxuS+3g2VEvz{8&^oSO^tfS0Iu7 zZjW3z9KI|O7oupE@TU6-MD=Gw>g#WCtgl_v@-Un^n@2FeqMFb;P>HftWoY;12y^3x z;+G-BzgsaIj)gxj!W#RzUv4Pf6qDV#YT6!xi9-?m96V2DL zZ03@0;b2_^rD+D}@XZ&;KjP`iDVy-P&j;2uxr(~VcSLDvo^WtX9E#;%BTacqtWP=w z?wJ2Y&41=xS;9Kjk$Qt%?>LfOT*zF28PGERE{-ty&F+;su-@S(`~2D7H+ zJI>**q8us~>IZK39iiviBHVE}hxMDH*?Y%k;RnlXEj*?UKgQNl+pb`Cu2j-7BNuWh z2N-L9$73XGuA>HdgHInApsw%Nic$I7>GF3SqNbUe3`ZiQG}(rQiL`{ffa3vbvVdQE3(RpOkbS`}4= zjdb*iTF^17gMXjW@oilW%$?89<;SyF9<&IS3^HH9ys0p3y8}9!vbl7$4c=+V0e#h3 z!te{nu#ePMyj7ABZ2m5%N#9cGQvWP+-RltFsxBk+_ibnXkO%1K@rq7-S6QJholh?B ztfVK}i%>tWi22GUa_%ROG3QYYK4Y#w|F6z4v%#0`LRk0V@pZOuwd8JzKPLW42Hff( zOE}+)Si;^3{I{mzyQ6w2Ss6qBvyH@z>er&lC&DpOV;E;{x(yx5v|#kP0&;6yJ3mu# z1-;*FFLy_0>>ZxhBf#{wD+zyJ$lv#H|7@Lk}=LyxTXYoXFAD1Oc6Prq(}ao z$p$Y?Nm5bti_Tcy2@^hEMAs!Fge}K3@Y=mr`ap6hUKp*$)m5?COUE7Zwfr&u+!~56 zQ>?gnIW6IT4kZ8YQ!EDey+sS0LtYP`O!r){!K?FLkTI_p;}ZK`k~KqyWz|Md8LcMt zPa22gov*RX_8Qh{a;5DdYHTC<0e4A`V2t_MY}TAl6YY}mRj3O$E%_8Ye!m5Gb^oLv zw^hQW6%^M@n<;EPQb3-cal*u1S5eIT5NiDz%P&6vl>U5K4NiUQaLgMs5jX2GN&a#T zxm3m)yFV2>X8k0l=RQ;MtU$(#lI0Gc90hL1b=X$4l9)TJhb-R~cx)aj>Jaw9x6ZA2 zW9)Zq=zfahAB4h0aebn?cLa5m>J-lHpcf4*QI+kdj@~>G=F5 z*nY8_OjS;xmWkuI!E+Sf`^>>{yDs6P=}{G3f+55;yn!8@h;LY1N!?GW2v4hd=Cd8b z_9@S4*J%YV{p)=8SxKP=>+wAdc}OzaUxMVDo$OuuG{q?g@W$*$bcO(U-#8VH$6w_e z)4K@Q=S6V-Ix@}R2gXcPgwlZbK-RWG+RI|P;F|^azunQ1@s=3i)c~c+>GYGy3ye8@ zivO!mA$U|U;~b{Lz0IcVEV;t+x(%XTYer*4TONDPtKyT|^LS;^bu!-h8%cFPNtHS) z`DKSZh|CoQPD#pzo!i;y7<^Tv^vDwDjLac^fgky=-(%^Eq_ybNWlVyTuCYI>jSzgg zftnqz#J-gRbfi_`=PNVNr0_YYYW30@=2PCr3cNELIxKEpo8sphrWm`X- z4SxWV8FqNcwF@p}Ws#NN4C$W$CKt}k!rP@&@JZ=7@>r{!=v-POI_LQUGwtO$4U6X} zibLA8Z;YU6Y9Z=YI%&Q*R|b;j_47@Rj4M3G7#B-Q;=bFlB#-s4^{Z;2b#@y0oV%Om znn=Ta?_}cZsEIp!S*~-mG095#1;4WXTF{h(Y(72}e<~kC!|0)$PHP-oVZ*wAtC%M% zeg!woFb2vt{QbX*&O0o}FO1_VrHH0VG=+#XMfIHfF|tBPWtMCuL{>&=Deb*?Dh(si z(0lHOh@wzNLuIeXin4#__pi&v70>%V=iK-A`}v@g^Fqexv898a*OT^`3LLyjjvKhz zN9!A$@L$|X(fh}7M8d@%98^5uKoH9ZUv+~kqgH@NT|5?+#G&uTM_A*Xjt#1MtXq4P z%q|Y4w&IU5=1>~h+7bya7eKh~)rOCK8sL#)6Z+2o$QU$Q>{)Y!mq!=jp>#<_?}2EK;zNJ@pcUgA$#8pr+?92XzTV>kxv}Hu zlSfM-(8U|&4!@;Ugo`$=Df0w13fc5ow&#@z$ViKnjn=13Qo+?(W)St?Gy}GUIgLv1q}7H!2>fb_h4Vp zJP1y!L{-IS@JQtu1U72Zb!taJE5j2f)a=HB{nMzSqq6A9q$-d~%LbfSLY9mxg*Y@t zr#NxQ|1=+_ka${jsg}4dU&&kyjo7yO4*k)eP5Ml9SwE^4V?!0#XL|?PePJR$>tH(! zUoj2VUCE&B4sYnrC9e4A+bsOGsu0(?U4=JOy@>Opi!deO72I=b0yWk*(vy3^-r1|I zE$dZ8yDGe>3(I-#`g9%V{Pu^3(~@Dr$O4?6-3%H!%yZsk1=!0n;OpLyBh8YGRhlmv zGhPZDs|G{d)-AB5FSmwQdMXMm|4zomPZSy3+`xXX>&(I8f-_~W0d2iNockonq%nHJ z>8ZKw`_`Urb&1C5k9L#AeaYO$!;RefHQl1kMOj4Fawh5fqm8!3p6tA5fcGDL6>a!h zg{J1=_#!7C_81T6jhiO0Zc_;MIH>*a3=y?)*`&xv8kYB7Voa9pAl09Uorx{vYREa* zogB+;I;#q4zeg|^C*aszWufd#D)zk41FOg!xck1Dj%L@QTT&<3#cXB5}cOY?(a8=1g(QT23r za|_*lX(H4#jVD#t_JOiq6YF^t0hs&Z*ZOIg=WrfZo*4_LzB}T!L;8aIM^hTu^BIh1 zv-7HVFz^F&puwbsdOEBkQUPu#{`o4dw``>UUYEnd@ zKr?<~%)aE-weKL* z&&0!v^lCj;)~YR_~dvv)V9urv5Pa{Tv#ad z#|_3&%!hR}ULM}*8SqNOmlN)@7d|XR#>aPpC!5ByZcrmvJ71N(i{x3}MoqXo?+`I` zYJ^hT3V591hc7>VC-YvOAqv;k@XXu@%Jw+e6f#7}x6vT?FYA*#@*Ch|(Jt1lP=zk9 zMlc8%Ny8_Nr7gLqiEOL~d|7*f?JH7nxY=vs}SI$_^w1MqknLR*hZ5QolM(I(4lv?ITe+-W-o^Hyr(YFiz``Az1Jlv}g& zVFp>4Ai~6bQoKX|{F=MIVR%iN^{@t3!@e^--XC#;yjtBSy6PSRr%yytsWuZnlHEZy z&PdXgR;$Ub?Ct;?H-uIgt<=&!R>U#^(T4vb*)$QW!JYa`ryl><6#-~DOH z2(nKj7=5y?GDo`&gkdKB^}Ry}uFQmupXTHE6*uYal!KUio;kTyWnt@4aXiGhVm^w= zP=9*{s{-n2Pm%?3$vQ6WZUF3z7b4O7}%ZwWNjTE}L+tA1M2l{>0Tzmcc%M}}E z!LE%LYW_|hDR{iP3kl~XAy|GR9vRj_#Par2@8u&fJ4lhA*6@?#73KJWQ-(yMcMe}> zFcOv51meq0Q6O=9GR#Q7O>2*S!OW5`nEN^ik010PKPw-K%xuf3+o?g!p|upmZI=UX z9*T}~1CabRk621xAr6ZLGH=yrkQ^9_9YKfi=mBYAm7NFJzKz8GxCl1yV)uv)Y5t|Y zK1>&`k%B`=n^d&X`O7^lO3fur%S~zyk*}2P_29K7W0Tz0#~Giz8E4=!j5txs&U)MM zQ~p%2DB21UEA8>bi71pvVmbKnMi{(cDIVGE3_k5=n9s=!o*wgM_v}cBhihYF-Q;I$@X%JD7NDXl^0(EH3!1T3g$NNZIZ&-)_IWc8ipm){#h6K=2DM{Xxydn z4Ab1wq4VP#?Abk-G)GJkc3+x7JvE*}SWr1JVa}ZVOJ4tXY-r%RG;}j`hn?MzuvAq- zv`)CiojoVcPdtEdD!`Z1+JeOU=@QYs1b=ENwT*QnWnll$)3i0Sgsz?(i;Y|#bh_G8 z=g;%;fcz~Y^XCPf(0Pe{PyV3d0dmx@E(o{I|3)y~58{OHq~%8f+&?;sf0f_}?fW80 zf%rSz_$i;UbFSmhJFA5#6B9Z^_91N>5>0P?+k+3*roh(u*XcMigder?BHlZfByvpq zLQ`T#!^Y$=6#Y(u#fDP^<%8_~xsqT*a4439vfjH@FHu|)%1x?^X5HKZnrPi68Xr+Y z=jxdYL#tGHC=kfVQ5T8APS&Ajej(@TQPg|S6p~n?2^q&U_${saXyKW|c42d{{N`1- zob;+j+s6jz5?S8FX&RKQIEOLs+vrNCNI2uC4cfBO=>5+h-v2TJrP(?3Pfa2gFo&MW zjWT-2uoEWBeMkQ49L6;crt(q&jC~ux`rrbb-g+IC6YXR&*ZB&Q_zi8kzf!uV9< z$l~qkcydM- zn`27Du-+lul1Y99N)C|hs$&>IG=!Md>lxFf2Y0-k&hN3`3jv?vFlAU7dhf`kEv<$$ zcJFG`mmkk~`~@(MKLp;RpOa@&JUi>ZC(Dy8V57b}7Dkppot?B0q~px)IiHxnxdo$b zQmpK5&qay%haunPD(BwE*k*@AX;{Z}n$~skf4&+K-n)VB?_0E6rIopV*~6s~iIJYk zI8%QY^<0$=DTKu(X?7~ zs%drt+!oD&-RA;v$;Vk>a`Pw+ed<=BH+L91Z|+35tpnWMODWhiW4g%yS2h>horZdo znxL+>mTVULjM~jwbbHAR#zc)IB`@np-?BLT`?;%Ts9Oy5)Skp0``5z4SsO$?--1aS z^D32^*ul)!i}0h(2$N16!R`NQxoc-zNzcF0{EsQs*8kWn?gitvm3FU%$;WeG@`AsV z#1-Mizm@1`aSo8rJ}fuRW`W=uei%XyAEDR0kHfUJ zF`%%#pA5Nh9sFy1xCc$UMdQx@W}ib%e5rqs?YU0UuY=1V@n04(${vro`&c$OeH*52 z_XGdsP1tzQigeDI0>fmYs7&Z2lCdF>G3lmZPGvpZXp_aDsLM3s)gWG`Fp3P#P+{4p zNrFj?4JcLjP`%GNAg!$~=muBe;l++P;|w$=``rc%N)-4N*L_(o^Ubwrr984}idfR?WpZ20bnD&G%c*w;Uh zJjqBn|I3hG=-ox8+}(-e9eq)gWyL4kq)_FvWtS%}3?WjF!eGt2EE;4nns!Q!6S&XH zC|S@Y!iybrf7mT@dFW|4wycTqYYiB)d=brQ*;|uy;RxxO&W=+@r1(LnlVQx*DpAwY z$J`X>GJJocm1}Vy%UJBU*zB^A@lZ~I^vh&8qnJl->T8jfkvnkiq4D(7iEidm5XXqP zaB}X2CBT?#)bCY4P6}HEsm!bASp7>h5N;3GPd8whe;RXFCn3=#VE2mM*}Nx{PbYcO zUgwTl4{wnJZFe}IPDzy6brB1s{MgL7gv|#%z*x@$8dnd+v(BfuAj=)(;cqpvD8GpG zYp;ao1r?~Uteni8n8K;HOlI>6DUm{y6|8sM0pD8B;AS-?{*SRajG4%~uolDd6ON?8 zTjH>Fq9weY_>*k^;|gql$XMtuS#jJO`kpAv zxD2sT@({TcF{p^bqwcA~(F>=5@fWxdUUu ztj-_wVMIHTo8-z^_?=j~{U(Y9*AmA$joc!S1+2ReO|#{?sI1NeQMcP=aG5CuVj<&M zZs!Aab9)UpWdo?}xj>LKAA}93K8rMkFXZ{De;Dq~ymgK;Y{#rFG}#%`ZB1cF81rtV z`WSHDG8k=?)EI|&CBBP$L`t40@SSUS^11gnKzK8Y59*Slz9LuXOV|Tn`ABgPQoz%4`8qcddA{FK8!uBW=jCsp^OFowT z$^Eg=blnBR46Z`#-x%CHNR7X{VTbTZ=>qevC*f7QAQ*P^9NYiLL&*z&B718r6q=Wc zW`3M0te4mceSbsP^Kg&)Gmn{*Oad2wY?z=uA%L2H)`XwKmHGUS7p=uQI#Gx-X1t}r zcvSBLRf|1low$1wfAUr>mX|8BoR%}BhARrLD|VvfbvZ5}{v4cIxPml?KBG6i3Ck#5 zAk{fb@ypRG)UGswrq}30;>)*qXWIpmf3$^r(6a^lqYYrM?J&|t^3g%r9SmRD!?zkA zmLm+otG4U0s=EsO)E&Vkp%2x6nDehnJf{hj3q(^HG(Hj;_^(ror z`}a-2gfZ<+Pwu04rdDFtj6QN(T8g*Sv4%B?li-(>3VrF_gX_gJapG+&?zNaUwtn=4 zdt+yzm#jYAJ5WJFP0nNGz7RH-FhHHbz8HSwFVT^Zgd43Bk(;~->~+|lb;b@*PC6rU z$oeii?s6Q)t?U9rtq_rakP5!r&-d)@0J)(TT z`4V@(G~AlejydHgP+G^ATeVY$q#6JH-@iltJ~V+F0hMG<-e6%|{Vs@+V86B@g6=nO zBV)?0h|CiY!?lZY!iW>!FiqK!jL~0&y)MIeBR_U#b4+3X_a`B0;c;U2J(gwIo1j|= z5N$3z2i?D&q3CE3W_3OlIV>}R|1&&{djuPd-KDi~*i zx%?Vfb3q1XDja7S`fw;Gf#|~e^5;$7;N3|RP;R~VS54t( zt*=Gp@v=2zvt>X$gPl2Ax8Ud0FZjJ&gExD)A5C}OX0xDJeDk=Gv9%_kYnKGge`tzF zW?jSF1EYmUQ@0U^?6>qwk3C+wz5()P%)$sub>;>VLDUsPoR!c>T_ZH{>PTxG>9q!D zRtGWPTssKPpK12iTAbZ*ht8R{l({sI(A=?!Y(6yt{-51H&^gQQ?-PWj*N)+WK3)2w zW)F<^(d8a*3Zq*tzk$(HHvZ3k7qvN?!h()%uV$j;h74U z?2$tCYaKXmkSWx^zJj;D9LI}KUW%4aoWyJ9hhW;zdHmDf+g$r-Yj`=2@oMB2;uR+^ za=zQ16JkK{F^H?jLJ-GgG3JvK#tNPT~i;FEKCebof~z$2j4q$l3-^NDAD; z`1plf*!)c>zU2$!BPkG#*qJccY6<@(DH;3RpHr*CYt}X1l9ULck)OJ0?Z zV2-Ox9$)ELC({Zn`&MR9)3z0txqd2?+3Ck}h3-iIEmP}rFlgNc!PR;S%| zV&5-z*Ud{HKEWTjhl5H#zdg9DjgD)a#!-tO`yq5)s3DtS595O$T*lNlK5*o-h(?`Q$T)txsqCr`BxkM;tX2?% z>l#x@&I8t4Z?+~=Ra;=8YzqC=w2S+^X$UBEYYN@Q6NN<%b8&XKJfC0w|BR@M2$Bo1 z|NH>T4XeeV7gtearpgz@yk?%QUUHx&9!zw4iSpYdY!7EZq~ju)qp3khoFr5^j85%(%bZ#9pR_^}CI@^uTb~vr89mw)nzs=QPXd zQ|IEuv2XBM)JHTjXoo#!C+P0C0@=ct^Bw(_bcN#ug8Ig^?Stw6=UqI$--2e(n~y&h z>Z5wFBzgSkEwRp;$wv@r{?>@|#QxkuSn@fENZqfcQJ0G#b^m!98(zxTQuUnavkmOs ztp*Mr77)b6!k46{#JgxV2~$`=Bu|xKet8#{v9^a8&sxgPGh^VV&mY>GaGwNJY{k_! zk>FsQ3!ie!NYSMqc!hOo9eqDqZ=7R-n#)&!oo+MiHI#(gL*9YTJ0-rS#E!Rpr6;U5 zoDD|RqxqJpnOK_hkNkcTfsE$H$)&YWuk!uSc`*iRVlTkfVQ)C8)j5hyBKmRl1M<6iLiuatH)+WuUj=c|bNe;_V-j?2lQU#e38E*-aPjeQoaUqhJ=cq1)olm5u)9HF!Nrmj3k-`CWytxmR;i%RuhCiEGFQBfIr0y~0i_f0nl2j|H@yCf!Ula_h{ZG)d zZ-0>S_TlLCp_=o%HXiQJiy~&B68lUB@kdv-p!MTVU^GIC zU$*cQm_7PTYBx=Tc-OT!{+B=gHJAV{N6wSi%xRIi${U9at%I7?&P4UmLF*s3Q-n!3 zCxGebO>~uUA8i_?zzrXC3%t#>2_D;x852I?#b>G9@uorWaE}41=lP1N19ysAR29)+ z=?|2wRe+@{CPB!NR2;6+hU&Ek@#Rn#YRT)P%Hk?aEpY|zp{$kSiA|txG@qZF_!3`! zN~Wa+U6gO{hp5G0YIF;1VNKFVdMk6PXin}Bv`n}|Of($PIxvls)-@no7HHGR&<40U zZ2RTo%vGFbUJZH|wLx}BHI>mr(NTtY)nr?K6xCjD|J0QYqAEX&zK-J3n&wFB$^$wlDjP4W0>sXCt;)XlCQVxkM+ewrF%GO)e= zwxQgjkOVxNa2VCbei1d_9L?_7`cz5(3v=6!<21DOQTJpvJ@MxfO^wPR-cPh3^2I{D zJ5d>$>!NYos(a{YGoAdMX9$s}B5TASNE7JWj-Bq~(RZ_uPr2>n@Do(_p;D%Ng+xL)c8ymfqM>i;nXb<9cmb zh|tL*a%sh6((?(hFzGbt)Q{nYqOgr01?>?(s9x1$=su;5Rc0C7>&OeNx7{w9xT;qq zdQuO+#ZFNF6Ct$7XDJOExeV6m|G-7=Lok1QI2bJmBr`tBQJYG0<}*D>&d!df+YU&J zB!-?wtE;QPabqd^MWjIS?ZMP;-%U~acV$u2ztgaDpd0RP$OeNeKgrqyR`~IC8eJK& zl(9CYz_(fwRf5!*^V^QGJF}@uP%!sk@DEbMIIeY!4K%GD@N-ox^YL9I6D3Pw_|lW0 zFvbFG&&CtQoB$GYW*l>W%Lr12k)r%L?`YLBJxoJa6ivT@b3ewQirWDsv?m$mHIy$KQ9;}~hw_@shT{Uo!Q|%MHrix=2y+iU2W%-p*+Y-XjlOgEX$h@K zJS9Qy7Y_zo$dp^7$xxOAChYsv|iqa`-Y?m3O$J00cdfdhG8 z$!WOBEA^SeezSZcSN($Xa9A!{5xO47oGKA~I;?QT9|IxW_Zi*yrVKC0HUdnyfU4bt zammFTD3J64uVw?Z<6mR!Ki2E(Kg6rvpD9!;KeEnV^aooj#?sZB70KdYsaM#%t%g0CqV})JLJwFsDTum1~ z%>78cjx7hhyH9GwCvTwM7DTvk*AJ6FU8eh|1wiYDZd$x{2Xp>$5Ip4p+i&eB|7T0r zOPP`%Mov&4`<`X-?{O-pj^O9b8H~?X3h~J?TtCu?^OARDv=S6h`@!FUif`N2+0z*f=N*T z%^CmG`lQ4YS~1L=eeXPDen4|zeQ$1g3q`RX4XE*Uk!YBADGr=efeHOuc-Lh)x!w z_M*o)=Vv#|&gsCBs`IEDY|A(z7SMNfzK}kNb(Cdd$kW7js-Wq?IWRupA+KAcD@q;r zPEqGJEF1$G*2#GAjt*vpoFl`Z*HM{&$8dtmC#q8W8i|BD#;;$?n+;guj@j3Vrd=*X zvyNS&hAxgjeFkFQ{Qqy21GA%d@$H_usNOM*E>C{~#xoHo`!59dx%+Xzq@K3U@rG^h z=JTiA{(Nr{tZCF|AP_Pg zpOW9d`)Aa9?S{vf{lKmLINKFS!b6QnP}3dF932mtM^=kh*1b!NKNz6o&-XMIh3a$;m3Xu50icT=nB21P@b^_RwQpJ;S^AS;;Wb)oy@ zdNM+uXBoXd9O9}Al0k*2FB`zRE{wxpc!AE7sN^ILMuJAv84L}HCl(&xscPFXGR)@{ zkz;?(s)D&taQir@ZYZU;^?SG&YgNc-9>ucjE3l0LL8bfXr8Yq@vlM_C%#}a^?A`)6mZ;yBuX4 zJShx}JIv-|5m3K!6gv|dqU>5JSf_r9IVn$~!l+)lIyHlgX*>bfJ`6_19pe0v!;HsQ zaS->#KEn>T{kXPtqu}2$iF~$cA>s=rV@cF^U`%V4(=lL9k2acTZ}d=qe&GgW^}m;POX@33o^LB}^YV5#COI zPJ2pJ=zKebc$RgtKlGKc{SAatv!mc@7fU79?L`&a&!T(09@bu-C!{33B6~+HVdth_ zV6y5eY}h_nP?Pb5sd-9N$GV7kKJNq18H%vXlrb0%8PRv^%5l-?zxeG-E_1y86NzPy zg1GO~=)Bu;WNAbcm>+!wGyO|N#_Qk0?=T7Y+-J+L8aWdebZo*F1z+Mge>*vTyNQmI zh=3cZc@%xCVS4o>=7i0qAHMa9ELN!UA0COZ`T1E=m*+#L1|Gmdi|1rn-zU=YSXVgm zaGEf~X1w5PpUqF+bcrU*hte3mgLGf90R}}*$MYStF+uM+1UyJWAG;b7BXNm+uzuh& zr4KM7cqzG%SPa$QhA@uGExc5tE7-{S;{L8O6cXD>@V7J6^M(jZJsL!tG_&cSC9&Mv zIt@rtQp4P+BzSVUl#4$yhMM1rhYRD+{O?Q?d<=)u7_HCNOTPS~6HGP9sl_&Muh9+s zqIQtO@~w=0w3e|uKVx;qT~h6D2A>T_!<*K6I;b#*{oX(l-7Z{t3e+4(M`jfdV^0%P=&<3sInDLW6c z%m(R?VI8#};Y4t;rYRq@$h;RF+=qwFcxCr~_+)ef9zMOvoz*qP$DiDBXKpktOK-$$ z6whoG!RR&-<^hl7k;P$?(%QzC%*qP<(}7s>&@i4EOiAFT~D$P z0&yp%2%B4je0dU0DHJ=d_U!XXYumZ^w9H$7}2p*W;)+0@yo$fcJrEFtm-`Q|q?i z^D8HDb_{a{W=G-S$MZ{Wg)Y zpN^Kd>OjH%8TZ@V7#`W#fno7EvQILUPKk{MmEXFMSX4sS75=s^NVrG`uc)*(OOGd= zmaLO8J$*a#T&1AM>H)M&me%k*Fb|Kx1B}l^Ct`Y z&0}$3rv?67t;s1Lm`r!we@EBW2eW>D0r_M$1vW3AifRcbh(W(N$?i`eNk;6>xmg|y zHAkU)+BdKmJ(c-!k24lT9VUNco##b)*j+f1KAjjq&9)C=teXW8t3FK7T$W9%JXyyt znTlc^*_m~rH>Rw+L-ul8PJxECVF_PL@&m-&p+Je<+apAC`J-4~Lla5eo qK=CtIRjUrFlkU(NL$#?zkrJ_gyOpbqe@IrI9$-7`YC3FK1o;mSat4q9 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e8000_i2000/set.001/fparam.npy b/examples/fparam/data/e8000_i2000/set.001/fparam.npy new file mode 100644 index 0000000000000000000000000000000000000000..88b5d9b8d64050ab487a68e0647d713d100b1b2b GIT binary patch literal 528 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= gXCxM+0{I$-1_nBsItsN4WCN~^)du#Xc<_V)0A2;W1ONa4 literal 0 HcmV?d00001 diff --git a/examples/fparam/data/e8000_i2000/set.002/box.npy b/examples/fparam/data/e8000_i2000/set.002/box.npy new file mode 100644 index 0000000000000000000000000000000000000000..23b62d305eddfe4762846027b04e009b5d90b479 GIT binary patch literal 3728 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