azure-quantum 3.2.0__py3-none-any.whl → 3.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,316 +0,0 @@
1
- import warnings
2
-
3
- from azure.quantum.job.base_job import ContentType
4
- from azure.quantum.job.job import Job
5
- from azure.quantum.target.target import Target
6
- from azure.quantum.workspace import Workspace
7
- from azure.quantum.target.params import InputParams
8
- from typing import Any, Dict, Type, Union, List
9
- from .job import MicrosoftElementsDftJob
10
- from pathlib import Path
11
- import copy
12
- import json
13
- from collections import defaultdict
14
-
15
-
16
- class MicrosoftElementsDft(Target):
17
- """
18
- Microsoft Elements Dft target from the microsoft-elements provider.
19
- """
20
-
21
- target_names = [
22
- "microsoft.dft"
23
- ]
24
-
25
-
26
- def __init__(
27
- self,
28
- workspace: "Workspace",
29
- name: str = "microsoft.dft",
30
- **kwargs
31
- ):
32
- """
33
- Initializes a new DFT target.
34
-
35
- :param workspace: Associated workspace
36
- :type workspace: Workspace
37
- :param name: Target name
38
- """
39
- # There is only a single target name for this target
40
- assert name == self.target_names[0]
41
-
42
- # make sure to not pass argument twice
43
- kwargs.pop("provider_id", None)
44
-
45
- super().__init__(
46
- workspace=workspace,
47
- name=name,
48
- input_data_format="microsoft.xyz.v1",
49
- output_data_format="microsoft.dft-results.v1",
50
- provider_id="microsoft-elements",
51
- content_type=ContentType.text_plain,
52
- **kwargs
53
- )
54
-
55
-
56
- def submit(self,
57
- input_data: Any,
58
- name: str = "azure-quantum-dft-job",
59
- shots: int = None,
60
- input_params: Union[Dict[str, Any], InputParams, None] = None,
61
- **kwargs) -> MicrosoftElementsDftJob:
62
- """
63
- Submit DFT job to Azure Quantum Services.
64
-
65
- :param input_data: Input data
66
- :type input_data: Any
67
- :param name: Job name
68
- :type name: str
69
- :param shots: Number of shots. Ignored in DFT job. Defaults to None
70
- :type shots: int
71
- :param input_params: Input parameters
72
- :type input_params: Dict[str, Any]
73
- :return: Azure Quantum job
74
- :rtype: Job
75
- """
76
-
77
- if shots is not None:
78
- warnings.warn("The 'shots' parameter is ignored in Microsoft Elements Dft job.")
79
-
80
- if isinstance(input_data, list):
81
-
82
- if len(input_data) < 1:
83
- raise ValueError("Input data list has no elements.")
84
-
85
- if all(isinstance(task,str) for task in input_data):
86
- qcschema_data = self.assemble_qcschema_from_files(input_data, input_params)
87
-
88
- qcschema_blobs = {}
89
- for i in range(len(qcschema_data)):
90
- qcschema_blobs[f"inputData_{i}"] = self._encode_input_data(qcschema_data[i])
91
-
92
- toc_str = self._create_table_of_contents(input_data, list(qcschema_blobs.keys()))
93
- elif all(isinstance(task,dict) for task in input_data):
94
- qcschema_blobs = {}
95
- for i in range(len(input_data)):
96
- qcschema_blobs[f"inputData_{i}"] = self._encode_input_data(input_data[i])
97
- toc_str = '{"description": "QcSchema Objects were given for input."}'
98
- else:
99
- raise ValueError(f"Unsupported batch submission. Please use List[str] or List[dict].")
100
- toc = self._encode_input_data(toc_str)
101
-
102
- input_params = {} if input_params is None else input_params
103
- return self._get_job_class().from_input_data_container(
104
- workspace=self.workspace,
105
- name=name,
106
- target=self.name,
107
- input_data=toc,
108
- batch_input_blobs=qcschema_blobs,
109
- input_params={ 'numberOfFiles': len(input_data), "inputFiles": list(qcschema_blobs.keys()), **input_params },
110
- content_type=kwargs.pop('content_type', self.content_type),
111
- encoding=kwargs.pop('encoding', self.encoding),
112
- provider_id=self.provider_id,
113
- input_data_format=kwargs.pop('input_data_format', 'microsoft.qc-schema.v1'),
114
- output_data_format=kwargs.pop('output_data_format', self.output_data_format),
115
- session_id=self.get_latest_session_id(),
116
- **kwargs
117
- )
118
- else:
119
- return super().submit(
120
- input_data=input_data,
121
- name=name,
122
- shots=shots,
123
- input_params=input_params,
124
- **kwargs
125
- )
126
-
127
-
128
-
129
- @classmethod
130
- def assemble_qcschema_from_files(self, input_data: Union[List[str]], input_params: Dict) -> List[Dict]:
131
- """
132
- Convert a list of files to a list of QcSchema objects that are ready for submission.
133
-
134
- :param input_data: Input data
135
- :type input_data: List[str]
136
- :param input_params: Input parameters
137
- :type input_params: Dict[str, Any]
138
- :rtype: List[Dict]
139
- """
140
-
141
- self._check_file_paths(input_data)
142
-
143
- qcshema_objects = []
144
- for file in input_data:
145
- file_path = Path(file)
146
-
147
- file_data = file_path.read_text()
148
- if file_path.suffix == '.xyz':
149
- mol = self._xyz_to_qcschema_mol(file_data)
150
- new_qcschema = self._new_qcshema( input_params, mol )
151
- qcshema_objects.append(new_qcschema)
152
- elif file_path.suffix == '.json':
153
- if input_params is not None and len(input_params.keys()) > 0:
154
- warnings.warn('Input parameters were given along with a QcSchema file which contains parameters, using QcSchema parameters as is.')
155
- with open(file_path, 'r') as f:
156
- qcshema_objects.append( json.load(f) )
157
- else:
158
- raise ValueError(f"File type '{file_path.suffix}' for file '{file_path}' is not supported.")
159
-
160
- return qcshema_objects
161
-
162
- @classmethod
163
- def _check_file_paths( self, input_data: List[str]):
164
- """Check the file types and make sure they are supported by our parsers."""
165
-
166
- warn_task_count = 1000
167
- if len(input_data) >= warn_task_count:
168
- warnings.warn(f'Number of tasks is greater than {warn_task_count}.')
169
-
170
- supported_ext = ['.xyz', '.json']
171
- prev_ext = None
172
- for path_str in input_data:
173
- path = Path(path_str)
174
-
175
- if not path.exists():
176
- raise FileNotFoundError(f"File {path_str} does not exist.")
177
-
178
- if path.suffix not in supported_ext:
179
- raise ValueError(f"'{path.suffix}' file type is not supported. Please use one of {supported_ext}.")
180
-
181
- if prev_ext is not None and prev_ext != path.suffix:
182
- raise ValueError(f"Multiple file types were provided ('{path.suffix}', '{prev_ext}'). Please submit only one file type.")
183
- else:
184
- prev_ext = path.suffix
185
-
186
-
187
- @classmethod
188
- def _new_qcshema( self, input_params: Dict[str,Any], mol: Dict[str,Any], ) -> Dict[str, Any]:
189
- """
190
- Create a new default qcshema object.
191
- """
192
-
193
- self._sanity_check_params(input_params, mol)
194
-
195
- if input_params.get("driver").lower() == "go":
196
- copy_input_params = copy.deepcopy(input_params)
197
- copy_input_params["driver"] = "gradient"
198
- new_object = {
199
- "schema_name": "qcschema_optimization_input",
200
- "schema_version": 1,
201
- "initial_molecule": mol,
202
- }
203
- if copy_input_params.get("go_keywords"):
204
- new_object["keywords"] = copy_input_params.pop("go_keywords")
205
- new_object["input_specification"] = copy_input_params
206
- return new_object
207
- elif input_params.get("driver").lower() == "bomd":
208
- copy_input_params = copy.deepcopy(input_params)
209
- copy_input_params["driver"] = "gradient"
210
- new_object = {
211
- "schema_name": "madft_molecular_dynamics_input",
212
- "schema_version": 1,
213
- "initial_molecule": mol,
214
- }
215
- if copy_input_params.get("bomd_keywords"):
216
- new_object["keywords"] = copy_input_params.pop("bomd_keywords")
217
- new_object["input_specification"] = copy_input_params
218
- return new_object
219
- else:
220
- new_object = copy.deepcopy(input_params)
221
- new_object.update({
222
- "schema_name": "qcschema_input",
223
- "schema_version": 1,
224
- "molecule": mol,
225
- })
226
- return new_object
227
-
228
- @classmethod
229
- def _sanity_check_params(self, input_params, mol):
230
-
231
- # QM/MM is not supported for GO, BOMD and Hessian.
232
- driver = input_params.get("driver",'').lower()
233
- if driver in ["go", "bomd", "hessian"]:
234
- if "extras" in mol and "mm_charges" in mol["extras"]:
235
- raise ValueError(f"'{driver}' does not support QM/MM.")
236
-
237
- # Top level params
238
- self._check_dict_for_required_keys(input_params, 'input_params', ['driver', 'model'])
239
-
240
- # Check Model params
241
- self._check_dict_for_required_keys(input_params['model'], 'input_params["model"]', ['method', 'basis'])
242
-
243
-
244
- @classmethod
245
- def _check_dict_for_required_keys(self, input_params: dict, dict_name: str, required_keys: list[str]):
246
- """Check dictionary for required keys and if it doesn't have then raise ValueError."""
247
-
248
- for required_key in required_keys:
249
- if required_key not in input_params.keys():
250
- raise ValueError(f"Required key ({required_key}) was not provided in {dict_name}.")
251
-
252
- @classmethod
253
- def _xyz_to_qcschema_mol(self, file_data: str ) -> Dict[str, Any]:
254
- """
255
- Convert xyz format to qcschema molecule.
256
- """
257
-
258
- lines = file_data.split("\n")
259
- if len(lines) < 3 or not lines[0]:
260
- raise ValueError("Invalid xyz format.")
261
- n_atoms = int(lines.pop(0))
262
- comment = lines.pop(0)
263
- mol = defaultdict(list)
264
- mol['extras'] = defaultdict(list)
265
- bohr_to_angstrom = 0.52917721092
266
- for line in lines:
267
- if line:
268
- elements = line.split()
269
- if len(elements) == 4:
270
- symbol, x, y, z = elements
271
- mol["symbols"].append(symbol)
272
- mol["geometry"] += [float(x)/bohr_to_angstrom, float(y)/bohr_to_angstrom, float(z)/bohr_to_angstrom]
273
- elif len(elements) == 5:
274
- symbol, x, y, z, q = elements
275
- if symbol[0] != '-':
276
- raise ValueError("Invalid xyz format. Molecular Mechanics atoms requires '-' at the beginning of the atom type.")
277
- mol["extras"]["mm_symbols"].append(symbol.replace('-', ''))
278
- mol["extras"]["mm_geometry"] += [float(x)/bohr_to_angstrom, float(y)/bohr_to_angstrom, float(z)/bohr_to_angstrom]
279
- mol["extras"]["mm_charges"].append(float(q))
280
- else:
281
- raise ValueError("Invalid xyz format.")
282
- else:
283
- break
284
-
285
- # Convert defaultdict to dict
286
- mol = dict(mol)
287
- mol["extras"] = dict(mol["extras"])
288
-
289
- if len(mol["symbols"])+len(mol["extras"].get("mm_symbols",[])) != n_atoms:
290
- raise ValueError("Number of inputs does not match the number of atoms in xyz file.")
291
-
292
- return mol
293
-
294
- @classmethod
295
- def _get_job_class(cls) -> Type[Job]:
296
- return MicrosoftElementsDftJob
297
-
298
- @classmethod
299
- def _create_table_of_contents(cls, input_files: List[str], input_blobs: List[str]) -> Dict[str,Any]:
300
- """Create the table of contents for a batched job that contains a description of file and the mapping between the file names and the blob names"""
301
-
302
- assert len(input_files) == len(input_blobs), "Internal error: number of blobs is not that same as the number of files."
303
-
304
- toc = []
305
- for i in range(len(input_files)):
306
- toc.append(
307
- {
308
- "inputFileName": input_files[i],
309
- "qcschemaBlobName": input_blobs[i],
310
- }
311
- )
312
-
313
- return {
314
- "description": "This files contains the mapping between the xyz file name that were submitted and the qcschema blobs that are used for the calculation.",
315
- "tableOfContents": toc,
316
- }