azure-quantum 2.4.0__tar.gz → 2.5.0__tar.gz

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.
Files changed (84) hide show
  1. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/PKG-INFO +1 -1
  2. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/_version.py +1 -1
  3. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/microsoft/elements/dft/target.py +112 -29
  4. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/rigetti/target.py +0 -5
  5. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/version.py +1 -1
  6. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure_quantum.egg-info/PKG-INFO +1 -1
  7. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/README.md +0 -0
  8. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/__init__.py +0 -0
  9. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_authentication/__init__.py +0 -0
  10. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_authentication/_chained.py +0 -0
  11. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_authentication/_default.py +0 -0
  12. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_authentication/_token.py +0 -0
  13. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/__init__.py +0 -0
  14. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/_client.py +0 -0
  15. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/_configuration.py +0 -0
  16. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/_patch.py +0 -0
  17. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/_serialization.py +0 -0
  18. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/_vendor.py +0 -0
  19. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/models/__init__.py +0 -0
  20. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/models/_enums.py +0 -0
  21. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/models/_models.py +0 -0
  22. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/models/_patch.py +0 -0
  23. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/operations/__init__.py +0 -0
  24. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/operations/_operations.py +0 -0
  25. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_client/operations/_patch.py +0 -0
  26. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_constants.py +0 -0
  27. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/_workspace_connection_params.py +0 -0
  28. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/argument_types/__init__.py +0 -0
  29. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/argument_types/types.py +0 -0
  30. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/chemistry/__init__.py +0 -0
  31. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/cirq/__init__.py +0 -0
  32. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/cirq/job.py +0 -0
  33. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/cirq/service.py +0 -0
  34. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/cirq/targets/__init__.py +0 -0
  35. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/cirq/targets/ionq.py +0 -0
  36. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/cirq/targets/quantinuum.py +0 -0
  37. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/cirq/targets/target.py +0 -0
  38. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/job/__init__.py +0 -0
  39. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/job/base_job.py +0 -0
  40. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/job/filtered_job.py +0 -0
  41. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/job/job.py +0 -0
  42. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/job/job_failed_with_results_error.py +0 -0
  43. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/job/session.py +0 -0
  44. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/job/workspace_item.py +0 -0
  45. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/job/workspace_item_factory.py +0 -0
  46. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/qiskit/__init__.py +0 -0
  47. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/qiskit/backends/__init__.py +0 -0
  48. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/qiskit/backends/backend.py +0 -0
  49. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/qiskit/backends/ionq.py +0 -0
  50. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/qiskit/backends/qci.py +0 -0
  51. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/qiskit/backends/quantinuum.py +0 -0
  52. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/qiskit/backends/rigetti.py +0 -0
  53. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/qiskit/job.py +0 -0
  54. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/qiskit/provider.py +0 -0
  55. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/storage.py +0 -0
  56. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/__init__.py +0 -0
  57. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/ionq.py +0 -0
  58. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/microsoft/elements/__init__.py +0 -0
  59. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/microsoft/elements/dft/__init__.py +0 -0
  60. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/microsoft/elements/dft/job.py +0 -0
  61. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/microsoft/target.py +0 -0
  62. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/params.py +0 -0
  63. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/pasqal/__init__.py +0 -0
  64. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/pasqal/result.py +0 -0
  65. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/pasqal/target.py +0 -0
  66. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/quantinuum.py +0 -0
  67. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/rigetti/__init__.py +0 -0
  68. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/rigetti/result.py +0 -0
  69. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/target.py +0 -0
  70. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/target/target_factory.py +0 -0
  71. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure/quantum/workspace.py +0 -0
  72. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure_quantum.egg-info/SOURCES.txt +0 -0
  73. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure_quantum.egg-info/dependency_links.txt +0 -0
  74. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure_quantum.egg-info/requires.txt +0 -0
  75. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/azure_quantum.egg-info/top_level.txt +0 -0
  76. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/requirements-cirq.txt +0 -0
  77. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/requirements-dev.txt +0 -0
  78. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/requirements-pulser.txt +0 -0
  79. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/requirements-qiskit.txt +0 -0
  80. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/requirements-qsharp.txt +0 -0
  81. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/requirements-quil.txt +0 -0
  82. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/requirements.txt +0 -0
  83. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/setup.cfg +0 -0
  84. {azure_quantum-2.4.0 → azure_quantum-2.5.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: azure-quantum
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: Python client for Azure Quantum
5
5
  Home-page: https://github.com/microsoft/azure-quantum-python
6
6
  Author: Microsoft
@@ -6,4 +6,4 @@
6
6
  # Changes may cause incorrect behavior and will be lost if the code is regenerated.
7
7
  # --------------------------------------------------------------------------
8
8
 
9
- VERSION = "2.4.0"
9
+ VERSION = "2.5.0"
@@ -10,6 +10,7 @@ from .job import MicrosoftElementsDftJob
10
10
  from pathlib import Path
11
11
  import copy
12
12
  import json
13
+ from collections import defaultdict
13
14
 
14
15
 
15
16
  class MicrosoftElementsDft(Target):
@@ -78,22 +79,34 @@ class MicrosoftElementsDft(Target):
78
79
 
79
80
  if isinstance(input_data, list):
80
81
 
81
- qcschema_data = self._assemble_qcshema_from_files(input_data, input_params)
82
+ if len(input_data) < 1:
83
+ raise ValueError("Input data list has no elements.")
82
84
 
83
- qcschema_blobs = {}
84
- for i in range(len(qcschema_data)):
85
- qcschema_blobs[f"inputData_{i}"] = self._encode_input_data(qcschema_data[i])
86
-
87
- toc_str = self._create_table_of_contents(input_data, list(qcschema_blobs.keys()))
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].")
88
100
  toc = self._encode_input_data(toc_str)
89
101
 
102
+ input_params = {} if input_params is None else input_params
90
103
  return self._get_job_class().from_input_data_container(
91
104
  workspace=self.workspace,
92
105
  name=name,
93
106
  target=self.name,
94
107
  input_data=toc,
95
108
  batch_input_blobs=qcschema_blobs,
96
- input_params={ 'numberOfFiles': len(qcschema_data), "inputFiles": list(qcschema_blobs.keys()), **input_params },
109
+ input_params={ 'numberOfFiles': len(input_data), "inputFiles": list(qcschema_blobs.keys()), **input_params },
97
110
  content_type=kwargs.pop('content_type', self.content_type),
98
111
  encoding=kwargs.pop('encoding', self.encoding),
99
112
  provider_id=self.provider_id,
@@ -114,16 +127,22 @@ class MicrosoftElementsDft(Target):
114
127
 
115
128
 
116
129
  @classmethod
117
- def _assemble_qcshema_from_files(self, input_data: List[str], input_params: Dict) -> str:
130
+ def assemble_qcschema_from_files(self, input_data: Union[List[str]], input_params: Dict) -> List[Dict]:
118
131
  """
119
- Convert a list of files to a list of qcshema objects serialized in json.
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]
120
139
  """
121
140
 
141
+ self._check_file_paths(input_data)
142
+
122
143
  qcshema_objects = []
123
144
  for file in input_data:
124
145
  file_path = Path(file)
125
- if not file_path.exists():
126
- raise FileNotFoundError(f"File {file} does not exist.")
127
146
 
128
147
  file_data = file_path.read_text()
129
148
  if file_path.suffix == '.xyz':
@@ -136,17 +155,44 @@ class MicrosoftElementsDft(Target):
136
155
  with open(file_path, 'r') as f:
137
156
  qcshema_objects.append( json.load(f) )
138
157
  else:
139
- raise ValueError(f"File type '{file_path.suffix}' for file '{file_path}' is not supported. Please use xyz or QcSchema file formats.")
158
+ raise ValueError(f"File type '{file_path.suffix}' for file '{file_path}' is not supported.")
140
159
 
141
160
  return qcshema_objects
142
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
+
143
187
  @classmethod
144
188
  def _new_qcshema( self, input_params: Dict[str,Any], mol: Dict[str,Any], ) -> Dict[str, Any]:
145
189
  """
146
190
  Create a new default qcshema object.
147
191
  """
148
192
 
149
- if input_params.get("driver") == "go":
193
+ self._sanity_check_params(input_params, mol)
194
+
195
+ if input_params.get("driver").lower() == "go":
150
196
  copy_input_params = copy.deepcopy(input_params)
151
197
  copy_input_params["driver"] = "gradient"
152
198
  new_object = {
@@ -154,11 +200,11 @@ class MicrosoftElementsDft(Target):
154
200
  "schema_version": 1,
155
201
  "initial_molecule": mol,
156
202
  }
157
- if copy_input_params.get("keywords") and copy_input_params["keywords"].get("geometryOptimization"):
158
- new_object["keywords"] = copy_input_params["keywords"].pop("geometryOptimization")
203
+ if copy_input_params.get("go_keywords"):
204
+ new_object["keywords"] = copy_input_params.pop("go_keywords")
159
205
  new_object["input_specification"] = copy_input_params
160
206
  return new_object
161
- elif input_params.get("driver") == "bomd":
207
+ elif input_params.get("driver").lower() == "bomd":
162
208
  copy_input_params = copy.deepcopy(input_params)
163
209
  copy_input_params["driver"] = "gradient"
164
210
  new_object = {
@@ -166,8 +212,8 @@ class MicrosoftElementsDft(Target):
166
212
  "schema_version": 1,
167
213
  "initial_molecule": mol,
168
214
  }
169
- if copy_input_params.get("keywords") and copy_input_params["keywords"].get("molecularDynamics"):
170
- new_object["keywords"] = copy_input_params["keywords"].pop("molecularDynamics")
215
+ if copy_input_params.get("bomd_keywords"):
216
+ new_object["keywords"] = copy_input_params.pop("bomd_keywords")
171
217
  new_object["input_specification"] = copy_input_params
172
218
  return new_object
173
219
  else:
@@ -178,7 +224,34 @@ class MicrosoftElementsDft(Target):
178
224
  "molecule": mol,
179
225
  })
180
226
  return new_object
181
-
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
+ supported_drivers = ['energy', 'gradient', 'hessian', 'go', 'bomd']
244
+ if input_params['driver'] not in supported_drivers:
245
+ raise ValueError(f"Driver ({input_params['driver']}) is not supported. Please use one of {supported_drivers}.")
246
+
247
+
248
+ @classmethod
249
+ def _check_dict_for_required_keys(self, input_params: dict, dict_name: str, required_keys: list[str]):
250
+ """Check dictionary for required keys and if it doesn't have then raise ValueError."""
251
+
252
+ for required_key in required_keys:
253
+ if required_key not in input_params.keys():
254
+ raise ValueError(f"Required key ({required_key}) was not provided in {dict_name}.")
182
255
 
183
256
  @classmethod
184
257
  def _xyz_to_qcschema_mol(self, file_data: str ) -> Dict[str, Any]:
@@ -187,27 +260,37 @@ class MicrosoftElementsDft(Target):
187
260
  """
188
261
 
189
262
  lines = file_data.split("\n")
190
- if len(lines) < 3:
263
+ if len(lines) < 3 or not lines[0]:
191
264
  raise ValueError("Invalid xyz format.")
192
265
  n_atoms = int(lines.pop(0))
193
266
  comment = lines.pop(0)
194
- mol = {
195
- "geometry": [],
196
- "symbols": [],
197
- }
267
+ mol = defaultdict(list)
268
+ mol['extras'] = defaultdict(list)
198
269
  bohr_to_angstrom = 0.52917721092
199
270
  for line in lines:
200
271
  if line:
201
272
  elements = line.split()
202
- if len(elements) < 4:
273
+ if len(elements) == 4:
274
+ symbol, x, y, z = elements
275
+ mol["symbols"].append(symbol)
276
+ mol["geometry"] += [float(x)/bohr_to_angstrom, float(y)/bohr_to_angstrom, float(z)/bohr_to_angstrom]
277
+ elif len(elements) == 5:
278
+ symbol, x, y, z, q = elements
279
+ if symbol[0] != '-':
280
+ raise ValueError("Invalid xyz format. Molecular Mechanics atoms requires '-' at the beginning of the atom type.")
281
+ mol["extras"]["mm_symbols"].append(symbol.replace('-', ''))
282
+ mol["extras"]["mm_geometry"] += [float(x)/bohr_to_angstrom, float(y)/bohr_to_angstrom, float(z)/bohr_to_angstrom]
283
+ mol["extras"]["mm_charges"].append(float(q))
284
+ else:
203
285
  raise ValueError("Invalid xyz format.")
204
- symbol, x, y, z = elements
205
- mol["symbols"].append(symbol)
206
- mol["geometry"] += [float(x)/bohr_to_angstrom, float(y)/bohr_to_angstrom, float(z)/bohr_to_angstrom]
207
286
  else:
208
287
  break
288
+
289
+ # Convert defaultdict to dict
290
+ mol = dict(mol)
291
+ mol["extras"] = dict(mol["extras"])
209
292
 
210
- if len(mol["symbols"]) != n_atoms:
293
+ if len(mol["symbols"])+len(mol["extras"].get("mm_symbols",[])) != n_atoms:
211
294
  raise ValueError("Number of inputs does not match the number of atoms in xyz file.")
212
295
 
213
296
  return mol
@@ -32,8 +32,6 @@ class RigettiTarget(str, Enum):
32
32
 
33
33
  ANKAA_3 = "rigetti.qpu.ankaa-3"
34
34
 
35
- ANKAA_9Q_3 = "rigetti.qpu.ankaa-9q-3"
36
-
37
35
  def simulators() -> List[str]:
38
36
  """Returns a list of simulator targets"""
39
37
  return [
@@ -44,7 +42,6 @@ class RigettiTarget(str, Enum):
44
42
  """Returns a list of QPU targets"""
45
43
  return [
46
44
  RigettiTarget.ANKAA_3.value,
47
- RigettiTarget.ANKAA_9Q_3.value,
48
45
  ]
49
46
 
50
47
  def num_qubits(target_name) -> int:
@@ -54,8 +51,6 @@ class RigettiTarget(str, Enum):
54
51
  return 20
55
52
  elif target_name == RigettiTarget.ANKAA_3.value:
56
53
  return 84
57
- elif target_name == RigettiTarget.ANKAA_9Q_3.value:
58
- return 9
59
54
  else:
60
55
  raise ValueError(f"Unknown target {target_name}")
61
56
 
@@ -5,4 +5,4 @@
5
5
  # Copyright (c) Microsoft Corporation. All rights reserved.
6
6
  # Licensed under the MIT License.
7
7
  ##
8
- __version__ = "2.4.0"
8
+ __version__ = "2.5.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: azure-quantum
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: Python client for Azure Quantum
5
5
  Home-page: https://github.com/microsoft/azure-quantum-python
6
6
  Author: Microsoft
File without changes
File without changes
File without changes