cyrusyc commited on
Commit
5225959
1 Parent(s): d72faca

add notebooks

Browse files
mlip_arena/tasks/diatomics/alignn/run.ipynb ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "3200850a-b8fb-4f50-9815-16ae8da0f942",
7
+ "metadata": {
8
+ "tags": []
9
+ },
10
+ "outputs": [],
11
+ "source": [
12
+ "from ase import Atoms, Atom\n",
13
+ "from ase.io import read, write\n",
14
+ "from ase.data import chemical_symbols, covalent_radii, vdw_alvarez\n",
15
+ "from ase.parallel import paropen as open\n",
16
+ "\n",
17
+ "from pathlib import Path\n",
18
+ "import os\n",
19
+ "import numpy as np\n",
20
+ "from pymatgen.core import Element\n",
21
+ "from tqdm.auto import tqdm\n",
22
+ "import pandas as pd\n",
23
+ "\n",
24
+ "\n",
25
+ "from alignn.ff.ff import AlignnAtomwiseCalculator,default_path\n",
26
+ "\n",
27
+ "\n",
28
+ "model_path = default_path()\n",
29
+ "calc = AlignnAtomwiseCalculator(path=model_path, device='cuda')\n",
30
+ "\n",
31
+ "model_name = 'ALIGNN'\n"
32
+ ]
33
+ },
34
+ {
35
+ "cell_type": "code",
36
+ "execution_count": null,
37
+ "id": "90887faa-1601-4c4c-9c44-d16731471d7f",
38
+ "metadata": {
39
+ "scrolled": true,
40
+ "tags": []
41
+ },
42
+ "outputs": [],
43
+ "source": [
44
+ "\n",
45
+ "\n",
46
+ "for symbol in tqdm(chemical_symbols):\n",
47
+ " \n",
48
+ " s = set([symbol])\n",
49
+ " \n",
50
+ " if 'X' in s:\n",
51
+ " continue\n",
52
+ " \n",
53
+ " try:\n",
54
+ " atom = Atom(symbol)\n",
55
+ " rmin = covalent_radii[atom.number] * 0.95\n",
56
+ " rvdw = vdw_alvarez.vdw_radii[atom.number] if atom.number < len(vdw_alvarez.vdw_radii) else np.nan \n",
57
+ " rmax = 3.1 * rvdw if not np.isnan(rvdw) else 6\n",
58
+ " rstep = 0.01 #if rmin < 1 else 0.4\n",
59
+ "\n",
60
+ " a = 2 * rmax\n",
61
+ "\n",
62
+ " npts = int((rmax - rmin)/rstep)\n",
63
+ "\n",
64
+ " rs = np.linspace(rmin, rmax, npts)\n",
65
+ " e = np.zeros_like(rs)\n",
66
+ "\n",
67
+ " da = symbol + symbol\n",
68
+ "\n",
69
+ " out_dir = Path(str(da))\n",
70
+ "\n",
71
+ " os.makedirs(out_dir, exist_ok=True)\n",
72
+ "\n",
73
+ " skip = 0\n",
74
+ " \n",
75
+ " element = Element(symbol)\n",
76
+ " \n",
77
+ " try:\n",
78
+ " m = element.valence[1]\n",
79
+ " if element.valence == (0, 2):\n",
80
+ " m = 0\n",
81
+ " except:\n",
82
+ " m = 0\n",
83
+ " \n",
84
+ " \n",
85
+ " r = rs[0]\n",
86
+ " \n",
87
+ " positions = [\n",
88
+ " [a/2-r/2, a/2, a/2],\n",
89
+ " [a/2+r/2, a/2, a/2],\n",
90
+ " ]\n",
91
+ " \n",
92
+ " traj_fpath = out_dir / f\"{model_name}.extxyz\"\n",
93
+ "\n",
94
+ " if traj_fpath.exists():\n",
95
+ " traj = read(traj_fpath, index=\":\")\n",
96
+ " skip = len(traj)\n",
97
+ " atoms = traj[-1]\n",
98
+ " else:\n",
99
+ " # Create the unit cell with two atoms\n",
100
+ " atoms = Atoms(\n",
101
+ " da, \n",
102
+ " positions=positions,\n",
103
+ " # magmoms=magmoms,\n",
104
+ " cell=[a, a+0.001, a+0.002], \n",
105
+ " pbc=True\n",
106
+ " )\n",
107
+ " \n",
108
+ " print(atoms)\n",
109
+ "\n",
110
+ " calc = calc\n",
111
+ "\n",
112
+ " atoms.calc = calc\n",
113
+ "\n",
114
+ " for i, r in enumerate(tqdm(rs)):\n",
115
+ "\n",
116
+ " if i < skip:\n",
117
+ " continue\n",
118
+ "\n",
119
+ " positions = [\n",
120
+ " [a/2-r/2, a/2, a/2],\n",
121
+ " [a/2+r/2, a/2, a/2],\n",
122
+ " ]\n",
123
+ " \n",
124
+ " # atoms.set_initial_magnetic_moments(magmoms)\n",
125
+ " \n",
126
+ " atoms.set_positions(positions)\n",
127
+ "\n",
128
+ " e[i] = atoms.get_potential_energy()\n",
129
+ " \n",
130
+ " atoms.calc.results.update({\n",
131
+ " \"forces\": atoms.get_forces()\n",
132
+ " })\n",
133
+ "\n",
134
+ " write(traj_fpath, atoms, append=\"a\")\n",
135
+ " except Exception as e:\n",
136
+ " print(e)\n"
137
+ ]
138
+ },
139
+ {
140
+ "cell_type": "code",
141
+ "execution_count": null,
142
+ "id": "a0ac2c09-370b-4fdd-bf74-ea5c4ade0215",
143
+ "metadata": {},
144
+ "outputs": [],
145
+ "source": [
146
+ "\n",
147
+ "\n",
148
+ "df = pd.DataFrame(columns=['name', 'method', 'R', 'E', 'F', 'S^2'])\n",
149
+ "\n",
150
+ "for symbol in tqdm(chemical_symbols):\n",
151
+ " \n",
152
+ " da = symbol + symbol\n",
153
+ " \n",
154
+ " out_dir = Path(da)\n",
155
+ " \n",
156
+ " traj_fpath = out_dir / f\"{model_name}.extxyz\"\n",
157
+ "\n",
158
+ "\n",
159
+ " if traj_fpath.exists():\n",
160
+ " traj = read(traj_fpath, index=\":\")\n",
161
+ " else:\n",
162
+ " continue\n",
163
+ " \n",
164
+ " Rs, Es, Fs, S2s = [], [], [], []\n",
165
+ " for atoms in traj:\n",
166
+ " \n",
167
+ " vec = atoms.positions[1] - atoms.positions[0]\n",
168
+ " r = np.linalg.norm(vec)\n",
169
+ " e = atoms.get_potential_energy()\n",
170
+ " f = np.inner(vec/r, atoms.get_forces()[1])\n",
171
+ " # s2 = np.mean(np.power(atoms.get_magnetic_moments(), 2))\n",
172
+ " \n",
173
+ " Rs.append(r)\n",
174
+ " Es.append(e)\n",
175
+ " Fs.append(f)\n",
176
+ " # S2s.append(s2)\n",
177
+ " \n",
178
+ " data = {\n",
179
+ " 'name': da,\n",
180
+ " 'method': 'ALIGNN',\n",
181
+ " 'R': Rs,\n",
182
+ " 'E': Es,\n",
183
+ " 'F': Fs,\n",
184
+ " 'S^2': S2s\n",
185
+ " }\n",
186
+ "\n",
187
+ " df = pd.concat([df, pd.DataFrame([data])], ignore_index=True)\n",
188
+ "\n",
189
+ "json_fpath = 'homonuclear-diatomics.json'\n",
190
+ "\n",
191
+ "df.to_json(json_fpath, orient='records') "
192
+ ]
193
+ },
194
+ {
195
+ "cell_type": "code",
196
+ "execution_count": null,
197
+ "id": "e0dd4367-3dca-440f-a7a9-7fdd84183f2c",
198
+ "metadata": {
199
+ "tags": []
200
+ },
201
+ "outputs": [],
202
+ "source": [
203
+ "df"
204
+ ]
205
+ },
206
+ {
207
+ "cell_type": "code",
208
+ "execution_count": null,
209
+ "id": "4e6ae884-89f3-43f2-8fd9-19bf00c91566",
210
+ "metadata": {},
211
+ "outputs": [],
212
+ "source": []
213
+ }
214
+ ],
215
+ "metadata": {
216
+ "kernelspec": {
217
+ "display_name": "mlip-arena",
218
+ "language": "python",
219
+ "name": "mlip-arena"
220
+ },
221
+ "language_info": {
222
+ "codemirror_mode": {
223
+ "name": "ipython",
224
+ "version": 3
225
+ },
226
+ "file_extension": ".py",
227
+ "mimetype": "text/x-python",
228
+ "name": "python",
229
+ "nbconvert_exporter": "python",
230
+ "pygments_lexer": "ipython3",
231
+ "version": "3.11.8"
232
+ },
233
+ "widgets": {
234
+ "application/vnd.jupyter.widget-state+json": {
235
+ "state": {},
236
+ "version_major": 2,
237
+ "version_minor": 0
238
+ }
239
+ }
240
+ },
241
+ "nbformat": 4,
242
+ "nbformat_minor": 5
243
+ }
mlip_arena/tasks/diatomics/gpaw/run.ipynb ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "08ee4c09-7fb5-4ce1-a7a3-5c5c52a6d4d5",
7
+ "metadata": {
8
+ "tags": []
9
+ },
10
+ "outputs": [],
11
+ "source": [
12
+ "from ase import Atoms, Atom\n",
13
+ "from ase.io import read, write\n",
14
+ "from ase.data import chemical_symbols, covalent_radii, vdw_alvarez\n",
15
+ "from ase.parallel import paropen as open\n",
16
+ "from gpaw import GPAW, PW, FermiDirac, LCAO\n",
17
+ "from gpaw import Davidson\n",
18
+ "from gpaw import Mixer, MixerSum, MixerDif\n",
19
+ "from gpaw.directmin.etdm_lcao import LCAOETDM\n",
20
+ "from gpaw.cdft.cdft import CDFT\n",
21
+ "from pathlib import Path\n",
22
+ "import os\n",
23
+ "import numpy as np\n",
24
+ "from pymatgen.core import Element\n",
25
+ "from tqdm.auto import tqdm\n",
26
+ "import pandas as pd"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": null,
32
+ "id": "90887faa-1601-4c4c-9c44-d16731471d7f",
33
+ "metadata": {
34
+ "tags": []
35
+ },
36
+ "outputs": [],
37
+ "source": [
38
+ "\n",
39
+ "magnetism = 'NM'\n",
40
+ "\n",
41
+ "for symbol in tqdm(chemical_symbols):\n",
42
+ " \n",
43
+ " s = set([symbol])\n",
44
+ " \n",
45
+ " if 'X' in s:\n",
46
+ " continue\n",
47
+ " \n",
48
+ " try:\n",
49
+ " atom = Atom(symbol)\n",
50
+ " rmin = covalent_radii[atom.number] * 2 * 0.6\n",
51
+ " rvdw = vdw_alvarez.vdw_radii[atom.number] if atom.number < len(vdw_alvarez.vdw_radii) else np.nan \n",
52
+ " rmax = 3.1 * rvdw if not np.isnan(rvdw) else 6\n",
53
+ " rstep = 0.2 #if rmin < 1 else 0.4\n",
54
+ "\n",
55
+ " a = 2 * rmax\n",
56
+ "\n",
57
+ " npts = int((rmax - rmin)/rstep)\n",
58
+ "\n",
59
+ " rs = np.linspace(rmin, rmax, npts)\n",
60
+ " e = np.zeros_like(rs)\n",
61
+ "\n",
62
+ " da = symbol + symbol\n",
63
+ "\n",
64
+ " out_dir = Path(str(da + f\"_{magnetism}\"))\n",
65
+ "\n",
66
+ " os.makedirs(out_dir, exist_ok=True)\n",
67
+ "\n",
68
+ " skip = 0\n",
69
+ " \n",
70
+ " element = Element(symbol)\n",
71
+ " \n",
72
+ " try:\n",
73
+ " m = element.valence[1]\n",
74
+ " if element.valence == (0, 2):\n",
75
+ " m = 0\n",
76
+ " except:\n",
77
+ " m = 0\n",
78
+ " \n",
79
+ " \n",
80
+ " r = rs[0]\n",
81
+ " \n",
82
+ " positions = [\n",
83
+ " [a/2-r/2, a/2, a/2],\n",
84
+ " [a/2+r/2, a/2, a/2],\n",
85
+ " ]\n",
86
+ " \n",
87
+ " if magnetism == 'FM':\n",
88
+ " if m == 0:\n",
89
+ " continue\n",
90
+ " magmoms = [m, m]\n",
91
+ " elif magnetism == 'AFM':\n",
92
+ " if m == 0:\n",
93
+ " continue\n",
94
+ " magmoms = [m, -m]\n",
95
+ " elif magnetism == 'NM':\n",
96
+ " magmoms = [0, 0]\n",
97
+ " \n",
98
+ " traj_fpath = out_dir / \"traj.extxyz\"\n",
99
+ "\n",
100
+ " if traj_fpath.exists():\n",
101
+ " traj = read(traj_fpath, index=\":\")\n",
102
+ " skip = len(traj)\n",
103
+ " atoms = traj[-1]\n",
104
+ " else:\n",
105
+ " # Create the unit cell with two atoms\n",
106
+ " atoms = Atoms(\n",
107
+ " da, \n",
108
+ " positions=positions,\n",
109
+ " magmoms=magmoms,\n",
110
+ " cell=[a, a+0.001, a+0.002], \n",
111
+ " pbc=True\n",
112
+ " )\n",
113
+ " \n",
114
+ " print(atoms)\n",
115
+ " \n",
116
+ " restart_fpath = out_dir / 'restart.gpw'\n",
117
+ "\n",
118
+ " calc = GPAW(\n",
119
+ " mode=PW(1000),\n",
120
+ " xc='PBE',\n",
121
+ " spinpol=True,\n",
122
+ " # basis='dzp'\n",
123
+ " basis='szp(dzp)',\n",
124
+ " # h=0.25,\n",
125
+ " # nbands=0 if element.is_noble_gas else '110%',\n",
126
+ " hund=False,\n",
127
+ " mixer=MixerDif(0.01, 1, 1) if element.is_transition_metal else MixerDif(0.25, 3, 10),\n",
128
+ " eigensolver='cg', #'rmm-diis', #Davidson(3), # This solver can parallelize over bands Davidson(3), #\n",
129
+ " occupations=FermiDirac(0.0, fixmagmom=False), # if not element.is_metal else FermiDirac(0.2, fixmagmom=False),\n",
130
+ " # eigensolver=LCAOETDM(),\n",
131
+ " # # searchdir_algo={'name': 'l-bfgs-p', 'memory': 10}),\n",
132
+ " # occupations={'name': 'fixed-uniform'},\n",
133
+ " # mixer={'backend': 'no-mixing'},\n",
134
+ " # nbands='nao',\n",
135
+ " symmetry={'point_group': False},\n",
136
+ " txt=out_dir / 'out.txt',\n",
137
+ " convergence={\n",
138
+ " 'eigenstates': 1e-5,\n",
139
+ " 'density': 5e-3,\n",
140
+ " 'energy': 5e-4,\n",
141
+ " # 'bands': 4\n",
142
+ " },\n",
143
+ " # {'energy': 0.0005, # eV / electron\n",
144
+ " # 'density': 1.0e-4, # electrons / electron\n",
145
+ " # 'eigenstates': 4.0e-8, # eV^2 / electron\n",
146
+ " # 'bands': 'occupied'}\n",
147
+ " )\n",
148
+ " # calc.attach(calc.write, 10, restart_fpath, mode='all')\n",
149
+ "\n",
150
+ " atoms.calc = calc\n",
151
+ " \n",
152
+ " # cdft = CDFT(calc=calc, atoms=atoms, spinspin_regions= \n",
153
+ " # atoms.calc = cdft\n",
154
+ "\n",
155
+ " for i, r in enumerate(tqdm(np.flip(rs))):\n",
156
+ "\n",
157
+ " if i < skip:\n",
158
+ " continue\n",
159
+ "\n",
160
+ " positions = [\n",
161
+ " [a/2-r/2, a/2, a/2],\n",
162
+ " [a/2+r/2, a/2, a/2],\n",
163
+ " ]\n",
164
+ " \n",
165
+ " # if i > 0: \n",
166
+ " # magmoms = atoms.get_magnetic_moments()\n",
167
+ " # m = min(abs(magmoms[0])*1.2, m)\n",
168
+ " # magmoms = magmoms*m/np.abs(magmoms)\n",
169
+ " \n",
170
+ " atoms.set_initial_magnetic_moments(magmoms)\n",
171
+ " \n",
172
+ " atoms.set_positions(positions)\n",
173
+ "\n",
174
+ " e[i] = atoms.get_potential_energy()\n",
175
+ " \n",
176
+ " atoms.calc.results.update({\n",
177
+ " \"forces\": atoms.get_forces()\n",
178
+ " })\n",
179
+ "\n",
180
+ " write(traj_fpath, atoms, append=\"a\")\n",
181
+ " except Exception as e:\n",
182
+ " print(e)\n"
183
+ ]
184
+ },
185
+ {
186
+ "cell_type": "code",
187
+ "execution_count": null,
188
+ "id": "a0ac2c09-370b-4fdd-bf74-ea5c4ade0215",
189
+ "metadata": {},
190
+ "outputs": [],
191
+ "source": [
192
+ "\n",
193
+ "\n",
194
+ "df = pd.DataFrame(columns=['name', 'method', 'R', 'E', 'F', 'S^2'])\n",
195
+ "\n",
196
+ "\n",
197
+ "\n",
198
+ "for symbol in tqdm(chemical_symbols):\n",
199
+ " \n",
200
+ " for magnetism in ['AFM', 'FM', 'NM']:\n",
201
+ " \n",
202
+ " da = symbol + symbol\n",
203
+ "\n",
204
+ " # out_dir = Path(da)\n",
205
+ " out_dir = Path(str(da + f\"_{magnetism}\"))\n",
206
+ "\n",
207
+ " traj_fpath = out_dir / \"traj.extxyz\"\n",
208
+ "\n",
209
+ " if traj_fpath.exists():\n",
210
+ " traj = read(traj_fpath, index=\":\")\n",
211
+ " else:\n",
212
+ " continue\n",
213
+ "\n",
214
+ " Rs, Es, Fs, S2s = [], [], [], []\n",
215
+ " for atoms in traj:\n",
216
+ "\n",
217
+ " vec = atoms.positions[1] - atoms.positions[0]\n",
218
+ " r = np.linalg.norm(vec)\n",
219
+ " e = atoms.get_potential_energy()\n",
220
+ " # f = np.inner(vec/r, atoms.get_forces()[1])\n",
221
+ " # s2 = np.mean(np.power(atoms.get_magnetic_moments(), 2))\n",
222
+ "\n",
223
+ " Rs.append(r)\n",
224
+ " Es.append(e)\n",
225
+ " # Fs.append(f)\n",
226
+ " # S2s.append(s2)\n",
227
+ "\n",
228
+ " data = {\n",
229
+ " 'name': da,\n",
230
+ " 'method': f'GGA-PBE (GPAW): {magnetism}',\n",
231
+ " 'R': Rs,\n",
232
+ " 'E': Es,\n",
233
+ " 'F': Fs,\n",
234
+ " 'S^2': S2s\n",
235
+ " }\n",
236
+ "\n",
237
+ " df = pd.concat([df, pd.DataFrame([data])], ignore_index=True)\n",
238
+ "\n",
239
+ "json_fpath = 'homonuclear-diatomics.json'\n",
240
+ "\n",
241
+ "df.to_json(json_fpath, orient='records') "
242
+ ]
243
+ },
244
+ {
245
+ "cell_type": "code",
246
+ "execution_count": null,
247
+ "id": "5d4a7312-a619-411f-9c6f-36c40cd47a34",
248
+ "metadata": {},
249
+ "outputs": [],
250
+ "source": []
251
+ }
252
+ ],
253
+ "metadata": {
254
+ "kernelspec": {
255
+ "display_name": "mlip-arena",
256
+ "language": "python",
257
+ "name": "mlip-arena"
258
+ },
259
+ "language_info": {
260
+ "codemirror_mode": {
261
+ "name": "ipython",
262
+ "version": 3
263
+ },
264
+ "file_extension": ".py",
265
+ "mimetype": "text/x-python",
266
+ "name": "python",
267
+ "nbconvert_exporter": "python",
268
+ "pygments_lexer": "ipython3",
269
+ "version": "3.11.8"
270
+ },
271
+ "widgets": {
272
+ "application/vnd.jupyter.widget-state+json": {
273
+ "state": {},
274
+ "version_major": 2,
275
+ "version_minor": 0
276
+ }
277
+ }
278
+ },
279
+ "nbformat": 4,
280
+ "nbformat_minor": 5
281
+ }
mlip_arena/tasks/diatomics/vasp/run.ipynb ADDED
@@ -0,0 +1,509 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "metadata": {
7
+ "tags": []
8
+ },
9
+ "outputs": [],
10
+ "source": [
11
+ "from datetime import timedelta\n",
12
+ "from typing import Union\n",
13
+ "import os\n",
14
+ "from pathlib import Path\n",
15
+ "from itertools import combinations, combinations_with_replacement\n",
16
+ "import psutil\n",
17
+ "import subprocess\n",
18
+ "\n",
19
+ "import numpy as np\n",
20
+ "import pandas as pd\n",
21
+ "import torch\n",
22
+ "from ase import Atoms, Atom\n",
23
+ "from ase.calculators.calculator import Calculator\n",
24
+ "from ase.calculators.singlepoint import SinglePointCalculator\n",
25
+ "from ase.calculators.vasp import Vasp\n",
26
+ "from ase.data import chemical_symbols, covalent_radii, vdw_alvarez\n",
27
+ "from ase.io import read, write\n",
28
+ "from dask.distributed import Client\n",
29
+ "from dask_jobqueue import SLURMCluster\n",
30
+ "from prefect import flow, task\n",
31
+ "from prefect.tasks import task_input_hash\n",
32
+ "from prefect_dask import DaskTaskRunner\n",
33
+ "\n",
34
+ "from pymatgen.core import Element\n",
35
+ "from pymatgen.io.ase import AseAtomsAdaptor\n",
36
+ "from pymatgen.io.vasp.inputs import Kpoints\n",
37
+ "from pymatgen.command_line.chargemol_caller import ChargemolAnalysis\n",
38
+ "\n",
39
+ "from mlip_arena.models import MLIPCalculator\n",
40
+ "from mlip_arena.models.utils import EXTMLIPEnum, MLIPMap, external_ase_calculator\n",
41
+ "from jobflow import run_locally\n",
42
+ "\n",
43
+ "from atomate2.vasp.jobs.mp import MPGGAStaticMaker\n",
44
+ "from atomate2.vasp.sets.mp import MPGGAStaticSetGenerator"
45
+ ]
46
+ },
47
+ {
48
+ "cell_type": "code",
49
+ "execution_count": null,
50
+ "metadata": {
51
+ "scrolled": true,
52
+ "tags": []
53
+ },
54
+ "outputs": [],
55
+ "source": [
56
+ "\n",
57
+ "nodes_per_alloc = 1\n",
58
+ "cpus_per_task = 16\n",
59
+ "gpus_per_node = 1\n",
60
+ "# tasks_per_node = int(128/4)\n",
61
+ "ntasks = 1\n",
62
+ "\n",
63
+ "\n",
64
+ "cluster_kwargs = dict(\n",
65
+ " cores=1,\n",
66
+ " memory=\"64 GB\",\n",
67
+ " shebang=\"#!/bin/bash\",\n",
68
+ " account=\"m3828\",\n",
69
+ " walltime=\"01:00:00\",\n",
70
+ " # processes=16,\n",
71
+ " # nanny=True,\n",
72
+ " job_mem=\"0\",\n",
73
+ " job_script_prologue=[\n",
74
+ " \"source ~/.bashrc\",\n",
75
+ " \"module load python\",\n",
76
+ " \"source activate /pscratch/sd/c/cyrisinstance.conda/mlip-arena\",\n",
77
+ " \"module load vasp/6.4.1-gpu\",\n",
78
+ " \n",
79
+ " \"export DDEC6_ATOMIC_DENSITIES_DIR='/global/homes/c/cyrusyc/chargemol/atomic_densities/'\",\n",
80
+ " f\"export OMP_NUM_THREADS={gpus_per_node}\",\n",
81
+ " \"export OMP_PLACES=threads\",\n",
82
+ " \"export OMP_PROC_BIND=spread\",\n",
83
+ " f\"export ASE_VASP_COMMAND='srun -n {ntasks} -c {4*gpus_per_node} --cpu-bind=cores --gpus-per-node {gpus_per_node} vasp_ncl'\"\n",
84
+ " # f\"export ASE_VASP_COMMAND='srun -N {nodes_per_alloc} --ntasks-per-node={tasks_per_node} -c {cpus_per_task} --cpu-bind=cores vasp_std'\"\n",
85
+ " # \"export ATOMATE2_CONFIG_FILE='/global/homes/c/cyrusyc/atomate2/config/atomate2-prefect-cpu-node.yaml'\"\n",
86
+ " ],\n",
87
+ " job_directives_skip=[\"-n\", \"--cpus-per-task\", \"-J\"],\n",
88
+ " job_extra_directives=[\n",
89
+ " \"-J diatomics\",\n",
90
+ " \"-q regular\",\n",
91
+ " f\"-N {nodes_per_alloc}\",\n",
92
+ " \"-C gpu\",\n",
93
+ " # \"-n 1\",\n",
94
+ " # \"-c 16\",\n",
95
+ " # \"--gpus-per-task=1\",\n",
96
+ " # \"--threads-per-task=1\",\n",
97
+ " # \"--gpu-bind=single:1\",\n",
98
+ " # \"--comment=00:20:00\",\n",
99
+ " # \"--time-min=00:05:00\",\n",
100
+ " # \"--signal=B:USR1@60\",\n",
101
+ " # \"--requeue\",\n",
102
+ " # \"--open-mode=append\",\n",
103
+ " # \"--mail-type=end,requeue\",\n",
104
+ " # \"--mail-user=cyrusyc@lbl.gov\",\n",
105
+ " ],\n",
106
+ " # python=\"srun python\",\n",
107
+ " death_timeout=86400, #float('inf')\n",
108
+ ")\n",
109
+ "\n",
110
+ "\n",
111
+ "cluster = SLURMCluster(**cluster_kwargs)\n",
112
+ "print(cluster.job_script())\n",
113
+ "# cluster.scale(3)\n",
114
+ "cluster.adapt(minimum_jobs=50, maximum_jobs=100)\n",
115
+ "client = Client(cluster)"
116
+ ]
117
+ },
118
+ {
119
+ "cell_type": "code",
120
+ "execution_count": null,
121
+ "metadata": {
122
+ "tags": []
123
+ },
124
+ "outputs": [],
125
+ "source": [
126
+ "\n",
127
+ "\n",
128
+ "@task(cache_key_fn=task_input_hash, cache_expiration=timedelta(hours=24), log_prints=True)\n",
129
+ "def calculate_single_diatomic(\n",
130
+ " calculator: str | EXTMLIPEnum | Calculator,\n",
131
+ " calculator_kwargs: dict | None,\n",
132
+ " atom1: str,\n",
133
+ " atom2: str,\n",
134
+ " rmin: float = 1.25,\n",
135
+ " rmax: float = 6.25,\n",
136
+ " rstep: float = 0.2,\n",
137
+ " magnetism: str = \"FM\"\n",
138
+ "):\n",
139
+ "\n",
140
+ " calculator_kwargs = calculator_kwargs or {}\n",
141
+ "\n",
142
+ " if isinstance(calculator, str) and calculator.lower() == 'vasp-mp-gga':\n",
143
+ " calc = Vasp(**calculator_kwargs)\n",
144
+ " calc.name = 'vasp-mp-gga'\n",
145
+ " # calc.name='atomate2'\n",
146
+ " elif isinstance(calculator, EXTMLIPEnum) and calculator in EXTMLIPEnum:\n",
147
+ " calc = external_ase_calculator(calculator, **calculator_kwargs)\n",
148
+ " elif calculator in MLIPMap:\n",
149
+ " calc = MLIPMap[calculator](**calculator_kwargs)\n",
150
+ " elif issubclass(calculator, Calculator):\n",
151
+ " calc = calculator(**calculator_kwargs)\n",
152
+ "\n",
153
+ " a = 2 * rmax\n",
154
+ "\n",
155
+ " npts = int((rmax - rmin)/rstep)\n",
156
+ "\n",
157
+ " rs = np.linspace(rmin, rmax, npts)\n",
158
+ " e = np.zeros_like(rs)\n",
159
+ " f = np.zeros_like(rs)\n",
160
+ "\n",
161
+ " da = atom1 + atom2\n",
162
+ " \n",
163
+ " assert isinstance(calc, Calculator)\n",
164
+ " \n",
165
+ " out_dir = Path(str(da + f\"_{magnetism}\"))\n",
166
+ " os.makedirs(out_dir, exist_ok=True)\n",
167
+ " \n",
168
+ " calc.directory = out_dir\n",
169
+ " \n",
170
+ " print(f\"write output to {calc.directory}\")\n",
171
+ " \n",
172
+ " element = Element(atom1)\n",
173
+ " \n",
174
+ " try:\n",
175
+ " m = element.valence[1]\n",
176
+ " if element.valence == (0, 2):\n",
177
+ " m = 0\n",
178
+ " except:\n",
179
+ " m = 0\n",
180
+ " \n",
181
+ " r = rs[0]\n",
182
+ " \n",
183
+ " positions = [\n",
184
+ " [a/2-r/2, a/2, a/2],\n",
185
+ " [a/2+r/2, a/2, a/2],\n",
186
+ " ]\n",
187
+ " \n",
188
+ " if magnetism == 'FM':\n",
189
+ " if m == 0:\n",
190
+ " return {}\n",
191
+ " magmoms = [m, m]\n",
192
+ " elif magnetism == 'AFM':\n",
193
+ " if m == 0:\n",
194
+ " return {}\n",
195
+ " magmoms = [m, -m]\n",
196
+ " elif magnetism == 'NM':\n",
197
+ " magmoms = [0, 0]\n",
198
+ " \n",
199
+ " traj_fpath = out_dir / \"traj.extxyz\"\n",
200
+ " \n",
201
+ " skip = 0\n",
202
+ " if traj_fpath.exists():\n",
203
+ " traj = read(traj_fpath, index=\":\")\n",
204
+ " skip = len(traj)\n",
205
+ " atoms = traj[-1]\n",
206
+ " else:\n",
207
+ " atoms = Atoms(\n",
208
+ " da, \n",
209
+ " positions=positions,\n",
210
+ " magmoms=magmoms,\n",
211
+ " cell=[a, a+0.001, a+0.002], \n",
212
+ " pbc=True\n",
213
+ " )\n",
214
+ " \n",
215
+ " # \n",
216
+ " \n",
217
+ " structure = AseAtomsAdaptor().get_structure(atoms)\n",
218
+ " \n",
219
+ " if magnetism == 'FM':\n",
220
+ " I_CONSTRAINED_M = 2\n",
221
+ " LAMBDA = 10\n",
222
+ " M_CONSTR = [0, 0, 1, 0, 0, 1] # \" \".join(map(str, [0, 0, 1])) + \" \" + \" \".join(map(str, [0, 0, 1]))\n",
223
+ " elif magnetism == 'AFM':\n",
224
+ " I_CONSTRAINED_M = 2\n",
225
+ " LAMBDA = 10\n",
226
+ " M_CONSTR = [0, 0, 1, 0, 0, -1] # \" \".join(map(str, [0, 0, 1])) + \" \" + \" \".join(map(str, [0, 0, -1]))\n",
227
+ " elif magnetism == 'NM':\n",
228
+ " I_CONSTRAINED_M = 1\n",
229
+ " LAMBDA = 10\n",
230
+ " M_CONSTR = [0, 0, 0, 0, 0, 0] #\" \".join(map(str, [0, 0, 0])) + \" \" + \" \".join(map(str, [0, 0, 0]))\n",
231
+ "\n",
232
+ " input_set_generator = MPGGAStaticSetGenerator(\n",
233
+ " user_incar_settings=dict(\n",
234
+ " ISYM = 0, # symmetry is off\n",
235
+ " ISPIN = 2,\n",
236
+ " ISMEAR = 0, # Gaussian smearing, otherwise negative occupancies might come up\n",
237
+ " SIGMA = 0.002, # tiny smearing width to safely break symmetry\n",
238
+ " AMIX = 0.2, # mixing set manually\n",
239
+ " BMIX = 0.0001,\n",
240
+ " LSUBROT= True, # spin orbit coupling (non collinear)\n",
241
+ " ALGO = \"Accurate\",\n",
242
+ " PREC = \"High\",\n",
243
+ " ENCUT = 1000,\n",
244
+ " ENAUG = 2000,\n",
245
+ " ISTART = 1,\n",
246
+ " ICHARG = 1,\n",
247
+ " NELM = 200,\n",
248
+ " TIME = 0.2,\n",
249
+ " LELF = False,\n",
250
+ " LMAXMIX=max(max(map(lambda a: \"spdf\".index(Element(a.symbol).block) * 2, atoms)), 2),\n",
251
+ " LMIXTAU=False,\n",
252
+ " VOSKOWN = 1,\n",
253
+ " I_CONSTRAINED_M = I_CONSTRAINED_M,\n",
254
+ " M_CONSTR = M_CONSTR,\n",
255
+ " LAMBDA = LAMBDA,\n",
256
+ " # performance\n",
257
+ " # lplane=False,\n",
258
+ " # npar=int(sqrt(ncpus)),\n",
259
+ " # nsim=1,\n",
260
+ " # LPLANE = True,\n",
261
+ " # # NCORE = 128,\n",
262
+ " # LSCALU = False,\n",
263
+ " # NSIM = 4,\n",
264
+ " # LPLANE = False,\n",
265
+ " # NPAR = 16,\n",
266
+ " # NSIM = 1,\n",
267
+ " # LSCALU = False,\n",
268
+ " # GPU\n",
269
+ " KPAR = gpus_per_node,\n",
270
+ " NSIM = 64,\n",
271
+ "\n",
272
+ " LVTOT = False,\n",
273
+ " LAECHG = True, # AECCARs\n",
274
+ " LASPH = True, # aspherical charge density\n",
275
+ " LCHARG = True, # CHGCAR\n",
276
+ " LWAVE = True\n",
277
+ " ),\n",
278
+ " user_kpoints_settings=Kpoints(), # Gamma point only\n",
279
+ " user_potcar_settings={\n",
280
+ " \"Yb\": \"Yb_3\"\n",
281
+ " },\n",
282
+ " sort_structure=False\n",
283
+ " )\n",
284
+ "\n",
285
+ " vis = input_set_generator.get_input_set(structure=structure)\n",
286
+ " vis.incar.pop(\"MAGMOM\")\n",
287
+ "\n",
288
+ " incar = {key.lower(): value for key, value in vis.incar.items()} \n",
289
+ " calc.set(kpts=1, gamma=True, **incar)\n",
290
+ " \n",
291
+ " atoms.calc = calc\n",
292
+ "\n",
293
+ " for i, r in enumerate(np.flip(rs)):\n",
294
+ "\n",
295
+ " \n",
296
+ " if i < skip:\n",
297
+ " continue\n",
298
+ "\n",
299
+ " positions = [\n",
300
+ " [a/2-r/2, a/2, a/2],\n",
301
+ " [a/2+r/2, a/2, a/2],\n",
302
+ " ]\n",
303
+ " \n",
304
+ " if i > 0: \n",
305
+ " magmoms = atoms.get_magnetic_moments()\n",
306
+ " \n",
307
+ " atoms.set_initial_magnetic_moments(magmoms)\n",
308
+ " atoms.set_positions(positions)\n",
309
+ "\n",
310
+ " print(f\"{atoms} separated by {r} A ({i+1}/{len(rs)})\")\n",
311
+ " \n",
312
+ "\n",
313
+ " e[i] = atoms.get_potential_energy()\n",
314
+ " f[i] = np.inner(np.array([1, 0, 0]), atoms.get_forces()[1])\n",
315
+ " \n",
316
+ " atoms.calc.results.update(dict(\n",
317
+ " magmoms=atoms.get_magnetic_moments()\n",
318
+ " ))\n",
319
+ " \n",
320
+ " write(out_dir / \"traj.extxyz\", atoms, append=\"a\")\n",
321
+ " \n",
322
+ "# additional_results = {}\n",
323
+ " \n",
324
+ "# try:\n",
325
+ "# ncpus = psutil.cpu_count(logical=True)\n",
326
+ "# nthreads = os.environ[\"OMP_NUM_THREADS\"]\n",
327
+ "# subprocess.run([\"export\", f\"OMP_NUM_THREADS={ncpus}\"], shell=True)\n",
328
+ " \n",
329
+ "# ca = ChargemolAnalysis(path=out_dir)\n",
330
+ " \n",
331
+ "# if charges := ca.ddec_charges:\n",
332
+ "# additional_results[\"charges\"] = np.array(charges)\n",
333
+ "# if dipoles := ca.dipoles:\n",
334
+ "# additional_results[\"dipoles\"] = np.array(dipoles)\n",
335
+ "# if magmoms := ca.ddec_spin_moments:\n",
336
+ "# additional_results[\"magmoms\"] = np.array(magmoms)\n",
337
+ " \n",
338
+ "# subprocess.run([\"export\", f\"OMP_NUM_THREADS={nthreads}\"], shell=True)\n",
339
+ "# except:\n",
340
+ "# print(\"DDEC failed\")\n",
341
+ " \n",
342
+ " \n",
343
+ "# atoms.calc.results.update(additional_results)\n",
344
+ " \n",
345
+ " return {\"r\": rs, \"E\": e, \"F\": f, \"da\": da}\n",
346
+ "\n"
347
+ ]
348
+ },
349
+ {
350
+ "cell_type": "code",
351
+ "execution_count": null,
352
+ "metadata": {
353
+ "tags": []
354
+ },
355
+ "outputs": [],
356
+ "source": [
357
+ "@flow(task_runner=DaskTaskRunner(address=client.scheduler.address), log_prints=True)\n",
358
+ "def calculate_multiple_diatomics(calculator_name, calculator_kwargs):\n",
359
+ "\n",
360
+ " futures = []\n",
361
+ " for sa in chemical_symbols:\n",
362
+ " \n",
363
+ " s = set([sa])\n",
364
+ " \n",
365
+ " if 'X' in s:\n",
366
+ " continue\n",
367
+ " \n",
368
+ " atom = Atom(sa)\n",
369
+ " rmin = covalent_radii[atom.number] * 2 * 0.6\n",
370
+ " rvdw = vdw_alvarez.vdw_radii[atom.number] if atom.number < len(vdw_alvarez.vdw_radii) else np.nan \n",
371
+ " rmax = 3.1 * rvdw if not np.isnan(rvdw) else 6\n",
372
+ " rstep = 0.2 #if rmin < 1 else 0.4\n",
373
+ " \n",
374
+ " futures.append(\n",
375
+ " calculate_single_diatomic.submit(\n",
376
+ " calculator_name, calculator_kwargs, sa, sa,\n",
377
+ " rmin=rmin, rmax=rmax,\n",
378
+ " rstep=rstep,\n",
379
+ " magnetism=\"FM\"\n",
380
+ " # npts=16 if 'H' in s else 21\n",
381
+ " )\n",
382
+ " )\n",
383
+ " futures.append(\n",
384
+ " calculate_single_diatomic.submit(\n",
385
+ " calculator_name, calculator_kwargs, sa, sa,\n",
386
+ " rmin=rmin, rmax=rmax,\n",
387
+ " rstep=rstep, #0.1 if rmin < 1 else 0.25,\n",
388
+ " magnetism=\"AFM\"\n",
389
+ " # npts=16 if 'H' in s else 21\n",
390
+ " )\n",
391
+ " )\n",
392
+ " futures.append(\n",
393
+ " calculate_single_diatomic.submit(\n",
394
+ " calculator_name, calculator_kwargs, sa, sa,\n",
395
+ " rmin=rmin, rmax=rmax,\n",
396
+ " rstep=rstep, #0.1 if rmin < 1 else 0.25,\n",
397
+ " magnetism=\"NM\"\n",
398
+ " # npts=16 if 'H' in s else 21\n",
399
+ " )\n",
400
+ " )\n",
401
+ "# for sa, sb in combinations_with_replacement(chemical_symbols, 2):\n",
402
+ " \n",
403
+ "# if 'X' in set([sa, sb]):\n",
404
+ "# continue\n",
405
+ " \n",
406
+ "# futures.append(\n",
407
+ "# calculate_single_diatomic.submit(\n",
408
+ "# calculator_name, calculator_kwargs, sa, sb,\n",
409
+ "# rmin=0.5, rmax=6.5,\n",
410
+ "# npts=16\n",
411
+ "# )\n",
412
+ "# )\n",
413
+ "\n",
414
+ " return [i for future in futures for i in future.result()]\n",
415
+ "\n"
416
+ ]
417
+ },
418
+ {
419
+ "cell_type": "code",
420
+ "execution_count": null,
421
+ "metadata": {
422
+ "scrolled": true,
423
+ "tags": []
424
+ },
425
+ "outputs": [],
426
+ "source": [
427
+ "calculate_multiple_diatomics(\n",
428
+ " \"vasp-mp-gga\", \n",
429
+ " dict(\n",
430
+ " xc=\"pbe\",\n",
431
+ " kpts=1,\n",
432
+ " # Massively parallel machines (Cray)\n",
433
+ " # lplane=False,\n",
434
+ " # npar=int(sqrt(ncpus)),\n",
435
+ " # nsim=1\n",
436
+ " # Multicore modern linux machines\n",
437
+ " # lplane=True,\n",
438
+ " # npar=2,\n",
439
+ " # lscalu=False,\n",
440
+ " # nsim=4\n",
441
+ " )\n",
442
+ ")\n"
443
+ ]
444
+ },
445
+ {
446
+ "cell_type": "code",
447
+ "execution_count": null,
448
+ "metadata": {
449
+ "scrolled": true,
450
+ "tags": []
451
+ },
452
+ "outputs": [],
453
+ "source": [
454
+ "\n",
455
+ "calculate_homonuclear_diatomics(\n",
456
+ " \"vasp-mp-gga\", \n",
457
+ " dict(\n",
458
+ " xc=\"pbe\",\n",
459
+ " kpts=1,\n",
460
+ " # Massively parallel machines (Cray)\n",
461
+ " # lplane=False,\n",
462
+ " # npar=int(sqrt(ncpus)),\n",
463
+ " # nsim=1\n",
464
+ " # Multicore modern linux machines\n",
465
+ " # lplane=True,\n",
466
+ " # npar=2,\n",
467
+ " # lscalu=False,\n",
468
+ " # nsim=4\n",
469
+ " )\n",
470
+ ")\n"
471
+ ]
472
+ },
473
+ {
474
+ "cell_type": "code",
475
+ "execution_count": null,
476
+ "metadata": {},
477
+ "outputs": [],
478
+ "source": []
479
+ }
480
+ ],
481
+ "metadata": {
482
+ "kernelspec": {
483
+ "display_name": "mlip-arena",
484
+ "language": "python",
485
+ "name": "mlip-arena"
486
+ },
487
+ "language_info": {
488
+ "codemirror_mode": {
489
+ "name": "ipython",
490
+ "version": 3
491
+ },
492
+ "file_extension": ".py",
493
+ "mimetype": "text/x-python",
494
+ "name": "python",
495
+ "nbconvert_exporter": "python",
496
+ "pygments_lexer": "ipython3",
497
+ "version": "3.11.8"
498
+ },
499
+ "widgets": {
500
+ "application/vnd.jupyter.widget-state+json": {
501
+ "state": {},
502
+ "version_major": 2,
503
+ "version_minor": 0
504
+ }
505
+ }
506
+ },
507
+ "nbformat": 4,
508
+ "nbformat_minor": 4
509
+ }