luna-quantum 0.0.16__py3-none-any.whl → 0.0.29__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.

Potentially problematic release.


This version of luna-quantum might be problematic. Click here for more details.

Files changed (58) hide show
  1. {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.29.dist-info}/METADATA +2 -1
  2. {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.29.dist-info}/RECORD +57 -53
  3. luna_sdk/controllers/custom_login_client.py +8 -3
  4. luna_sdk/controllers/luna_platform_client.py +9 -9
  5. luna_sdk/controllers/luna_q.py +7 -10
  6. luna_sdk/controllers/luna_solve.py +8 -15
  7. luna_sdk/controllers/luna_transform.py +9 -16
  8. luna_sdk/interfaces/circuit_repo_i.py +18 -12
  9. luna_sdk/interfaces/cplex_repo_i.py +25 -10
  10. luna_sdk/interfaces/info_repo_i.py +10 -3
  11. luna_sdk/interfaces/lp_repo_i.py +20 -8
  12. luna_sdk/interfaces/optimization_repo_i.py +31 -57
  13. luna_sdk/interfaces/qpu_token_repo_i.py +24 -28
  14. luna_sdk/interfaces/solutions_repo_i.py +44 -24
  15. luna_sdk/repositories/circuit_repo.py +11 -44
  16. luna_sdk/repositories/cplex_repo.py +32 -20
  17. luna_sdk/repositories/info_repo.py +4 -7
  18. luna_sdk/repositories/lp_repo.py +21 -15
  19. luna_sdk/repositories/optimization_repo.py +25 -200
  20. luna_sdk/repositories/qpu_token_repo.py +28 -120
  21. luna_sdk/repositories/solutions_repo.py +109 -181
  22. luna_sdk/schemas/create/solution.py +2 -2
  23. luna_sdk/schemas/optimization.py +9 -11
  24. luna_sdk/schemas/pretty_base.py +10 -3
  25. luna_sdk/schemas/qpu_token.py +3 -0
  26. luna_sdk/schemas/solution.py +7 -6
  27. luna_sdk/schemas/solver_info.py +31 -1
  28. luna_sdk/schemas/solver_parameters/aws/optimizer_params.py +40 -0
  29. luna_sdk/schemas/solver_parameters/aws/qaoa.py +36 -4
  30. luna_sdk/schemas/solver_parameters/base_parameter.py +5 -0
  31. luna_sdk/schemas/solver_parameters/dwave/base.py +15 -14
  32. luna_sdk/schemas/solver_parameters/dwave/dialectic_search.py +3 -2
  33. luna_sdk/schemas/solver_parameters/dwave/kerberos.py +2 -3
  34. luna_sdk/schemas/solver_parameters/dwave/leap_hybrid_bqm.py +2 -2
  35. luna_sdk/schemas/solver_parameters/dwave/leap_hybrid_cqm.py +2 -2
  36. luna_sdk/schemas/solver_parameters/dwave/parallel_tempering.py +2 -3
  37. luna_sdk/schemas/solver_parameters/dwave/parallel_tempering_qpu.py +2 -3
  38. luna_sdk/schemas/solver_parameters/dwave/population_annealing.py +2 -3
  39. luna_sdk/schemas/solver_parameters/dwave/population_annealing_qpu.py +2 -1
  40. luna_sdk/schemas/solver_parameters/dwave/qaga.py +4 -2
  41. luna_sdk/schemas/solver_parameters/dwave/qbsolv_like_qpu.py +2 -3
  42. luna_sdk/schemas/solver_parameters/dwave/qbsolv_like_simulated_annealing.py +2 -3
  43. luna_sdk/schemas/solver_parameters/dwave/qbsolv_like_tabu_search.py +2 -3
  44. luna_sdk/schemas/solver_parameters/dwave/quantum_annealing.py +2 -3
  45. luna_sdk/schemas/solver_parameters/dwave/repeated_reverse_quantum_annealing.py +4 -2
  46. luna_sdk/schemas/solver_parameters/dwave/repeated_reverse_simulated_annealing.py +3 -2
  47. luna_sdk/schemas/solver_parameters/dwave/saga.py +1 -1
  48. luna_sdk/schemas/solver_parameters/dwave/tabu_search.py +3 -1
  49. luna_sdk/schemas/solver_parameters/fujitsu/base.py +5 -4
  50. luna_sdk/schemas/solver_parameters/fujitsu/partial_config.py +7 -5
  51. luna_sdk/schemas/solver_parameters/ibm/standard_parameters.py +121 -7
  52. luna_sdk/schemas/solver_parameters/qctrl/qaoa.py +2 -2
  53. luna_sdk/schemas/wrappers/__init__.py +1 -0
  54. luna_sdk/schemas/wrappers/datetime_wrapper.py +31 -0
  55. luna_sdk/utils/parameter_finder.py +90 -0
  56. luna_sdk/constants.py +0 -1
  57. {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.29.dist-info}/LICENSE +0 -0
  58. {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.29.dist-info}/WHEEL +0 -0
@@ -26,13 +26,16 @@ class QpuTokenRepo(IQpuTokenRepo):
26
26
  else:
27
27
  return f"{self._endpoint}/organization"
28
28
 
29
- def _get_by_name(self, name: str, token_type: QpuTokenTypeEnum) -> QpuTokenOut:
29
+ def _get_by_name(
30
+ self, name: str, token_type: QpuTokenTypeEnum, **kwargs
31
+ ) -> QpuTokenOut:
30
32
  response: Response = self._client.get(
31
- f"{self._get_endpoint_by_type(token_type)}/by_name/{name}"
33
+ f"{self._get_endpoint_by_type(token_type)}/by_name/{name}", **kwargs
32
34
  )
33
35
  response.raise_for_status()
34
36
 
35
37
  qpu_token_data = response.json()
38
+ qpu_token_data["token_type"] = token_type
36
39
  return QpuTokenOut.model_validate(qpu_token_data)
37
40
 
38
41
  def create(
@@ -40,32 +43,10 @@ class QpuTokenRepo(IQpuTokenRepo):
40
43
  name: str,
41
44
  provider: str,
42
45
  token: str,
43
- token_type: QpuTokenTypeEnum = QpuTokenTypeEnum.PERSONAL,
46
+ token_type: QpuTokenTypeEnum,
44
47
  encryption_key: Optional[str] = None,
48
+ **kwargs,
45
49
  ) -> QpuTokenOut:
46
- """
47
- Create user QPU token
48
-
49
- Parameters
50
- ----------
51
- name: str
52
- Name of the QPU token
53
- provider: str
54
- Name of provider
55
- token: str
56
- Token
57
- token_type: QpuTokenTypeEnum
58
- There are two types of QPU tokens: PERSONAL and ORGANIZATION.
59
- The default value is PERSONAL.
60
- All users of an organization can use organization QPU tokens.
61
- User QPU tokens can only be used by the user who created them.
62
- encryption_key: Optional[str]
63
- Encryption key to be used for encryption of QPU tokens.
64
- Returns
65
- -------
66
- QpuTokenOut
67
- QpuTokenOut instances.
68
- """
69
50
  encryption_key = encryption_key or os.environ.get("LUNA_ENCRYPTION_KEY")
70
51
  if encryption_key is None:
71
52
  raise EncryptionNotSetException
@@ -77,10 +58,13 @@ class QpuTokenRepo(IQpuTokenRepo):
77
58
  )
78
59
 
79
60
  response: Response = self._client.post(
80
- self._get_endpoint_by_type(token_type), content=qpu_token.model_dump_json()
61
+ self._get_endpoint_by_type(token_type),
62
+ content=qpu_token.model_dump_json(),
63
+ **kwargs,
81
64
  )
82
65
  response.raise_for_status()
83
66
  qpu_token_data = response.json()
67
+ qpu_token_data["token_type"] = token_type
84
68
  return QpuTokenOut.model_validate(qpu_token_data)
85
69
 
86
70
  def get_all(
@@ -88,30 +72,8 @@ class QpuTokenRepo(IQpuTokenRepo):
88
72
  filter_provider: Optional[str] = None,
89
73
  name: Optional[str] = None,
90
74
  token_type: Optional[QpuTokenTypeEnum] = None,
75
+ **kwargs,
91
76
  ) -> Dict[QpuTokenTypeEnum, List[QpuTokenOut]]:
92
- """
93
- Retrieve list of user QPU tokens.
94
-
95
- Parameters
96
- ----------
97
- filter_provider: Optional[str]
98
- The provider for which qpu tokens should be retrieved
99
- name: Optional[str]
100
- Name of the QPU token that should be retrieved
101
- token_type: Optional[QpuTokenTypeEnum]
102
- If you want to retrieve only user or organization QPU tokens
103
- otherwise all QPU tokens will be retrieved
104
- token_type: QpuTokenTypeEnum
105
- There are two types of QPU tokens: PERSONAL and ORGANIZATION.
106
- The default value is PERSONAL.
107
- All users of an organization can use organization QPU tokens.
108
- User QPU tokens can only be used by the user who created them.
109
-
110
- Returns
111
- -------
112
- Dict[QpuTokenTypeEnum, List[QpuTokenOut]]
113
- List of QpuTokenOut instances.
114
- """
115
77
  params = {}
116
78
  if filter_provider:
117
79
  params["filter_provider"] = filter_provider
@@ -119,14 +81,19 @@ class QpuTokenRepo(IQpuTokenRepo):
119
81
  if name:
120
82
  params["name"] = name
121
83
 
122
- response = self._client.get(self._get_endpoint_by_type(), params=params)
84
+ response = self._client.get(
85
+ self._get_endpoint_by_type(), params=params, **kwargs
86
+ )
123
87
  response.raise_for_status()
124
88
 
125
89
  to_return: Dict[QpuTokenTypeEnum, List[QpuTokenOut]] = {}
126
90
  for key, value in response.json().items():
127
- to_return[QpuTokenTypeEnum(key)] = [
128
- QpuTokenOut.model_validate(item) for item in value
129
- ]
91
+ tokens = []
92
+ for item in value:
93
+ if token_type is None or token_type.value == key:
94
+ item["token_type"] = QpuTokenTypeEnum(key)
95
+ tokens.append(QpuTokenOut.model_validate(item))
96
+ to_return[QpuTokenTypeEnum(key)] = tokens
130
97
 
131
98
  return to_return
132
99
 
@@ -134,56 +101,15 @@ class QpuTokenRepo(IQpuTokenRepo):
134
101
  self,
135
102
  name: str,
136
103
  token_type: QpuTokenTypeEnum = QpuTokenTypeEnum.PERSONAL,
104
+ **kwargs,
137
105
  ) -> QpuTokenOut:
138
- """
139
- Retrieve user QPU token by id
140
-
141
- Parameters
142
- ----------
143
- name: str
144
- Name of the QPU token that should be retrieved
145
- token_type: QpuTokenTypeEnum
146
- There are two types of QPU tokens: PERSONAL and ORGANIZATION.
147
- The default value is PERSONAL.
148
- All users of an organization can use organization QPU tokens.
149
- User QPU tokens can only be used by the user who created them.
150
-
151
- Returns
152
- -------
153
- QpuTokenOut
154
- QpuTokenOut instance.
155
- """
156
-
157
- qpu_token: QpuTokenOut = self._get_by_name(name, token_type)
106
+ qpu_token: QpuTokenOut = self._get_by_name(name, token_type, **kwargs)
158
107
 
159
108
  return qpu_token
160
109
 
161
110
  def rename(
162
- self,
163
- name: str,
164
- new_name: str,
165
- token_type: QpuTokenTypeEnum = QpuTokenTypeEnum.PERSONAL,
111
+ self, name: str, new_name: str, token_type: QpuTokenTypeEnum, **kwargs
166
112
  ) -> QpuTokenOut:
167
- """
168
- Update user QPU token by id
169
-
170
- Parameters
171
- ----------
172
- name: str
173
- Name of the QPU token that should be updated
174
- new_name: str
175
- The new name
176
- token_type: QpuTokenTypeEnum
177
- There are two types of QPU tokens: PERSONAL and ORGANIZATION.
178
- The default value is PERSONAL.
179
- All users of an organization can use organization QPU tokens.
180
- User QPU tokens can only be used by the user who created them.
181
-
182
- Returns
183
- -------
184
- QpuTokenOut
185
- QpuTokenOut instance.
186
- """
187
113
  qpu_token_update_data = {"name": new_name}
188
114
 
189
115
  token: QpuTokenOut = self.get(name, token_type)
@@ -191,36 +117,18 @@ class QpuTokenRepo(IQpuTokenRepo):
191
117
  response = self._client.put(
192
118
  f"{self._get_endpoint_by_type(token_type)}/{token.id}",
193
119
  content=json.dumps(qpu_token_update_data),
120
+ **kwargs,
194
121
  )
195
122
  response.raise_for_status()
196
123
 
197
124
  qpu_token_data = response.json()
125
+ qpu_token_data["token_type"] = token_type
198
126
  return QpuTokenOut.model_validate(qpu_token_data)
199
127
 
200
- def delete(
201
- self,
202
- name: str,
203
- token_type: QpuTokenTypeEnum = QpuTokenTypeEnum.PERSONAL,
204
- ) -> None:
205
- """
206
- Delete organization QPU token by name
207
-
208
- Parameters
209
- ----------
210
- name: str
211
- Name of the QPU token that should be deleted
212
- token_type: QpuTokenTypeEnum
213
- There are two types of QPU tokens: PERSONAL and ORGANIZATION.
214
- The default value is PERSONAL.
215
- All users of an organization can use organization QPU tokens.
216
- User QPU tokens can only be used by the user who created them.
217
-
218
- Returns
219
- -------
220
- """
128
+ def delete(self, name: str, token_type: QpuTokenTypeEnum, **kwargs) -> None:
221
129
  token: QpuTokenOut = self.get(name, token_type)
222
130
 
223
131
  response = self._client.delete(
224
- f"{self._get_endpoint_by_type(token_type)}/{token.id}"
132
+ f"{self._get_endpoint_by_type(token_type)}/{token.id}", **kwargs
225
133
  )
226
134
  response.raise_for_status()
@@ -1,9 +1,9 @@
1
1
  import logging
2
2
  import os
3
3
  from time import sleep
4
- from typing import Dict, List, Optional, Union
4
+ from typing import Any, Dict, List, Optional, Type, Union
5
5
 
6
- from pydantic import BaseModel
6
+ from pydantic import BaseModel, ValidationError
7
7
 
8
8
  from luna_sdk.exceptions.encryption_exception import EncryptionNotSetException
9
9
  from luna_sdk.interfaces.solutions_repo_i import ISolutionsRepo
@@ -19,32 +19,15 @@ from luna_sdk.schemas.solution import (
19
19
  UseCaseRepresentation,
20
20
  UseCaseResult,
21
21
  )
22
+ from luna_sdk.utils.parameter_finder import get_parameter_by_solver
22
23
  from luna_sdk.utils.qpu_tokens import extract_qpu_tokens_from_env
23
24
 
24
25
 
25
26
  class SolutionsRepo(ISolutionsRepo):
26
27
  _endpoint = "/solutions"
27
28
 
28
- def get(
29
- self,
30
- solution_id: str,
31
- ) -> Solution:
32
- """
33
- Retrieve one solution by id.
34
-
35
- Parameters
36
- ----------
37
- solution_id: str
38
- Id of the solution that should be retrieved
39
-
40
- Returns
41
- -------
42
- Solution
43
- Solution instance
44
- """
45
- response = self._client.get(
46
- f"{self._endpoint}/{solution_id}",
47
- )
29
+ def get(self, solution_id: str, **kwargs) -> Solution:
30
+ response = self._client.get(f"{self._endpoint}/{solution_id}", **kwargs)
48
31
 
49
32
  response.raise_for_status()
50
33
 
@@ -56,26 +39,8 @@ class SolutionsRepo(ISolutionsRepo):
56
39
  limit: int = 50,
57
40
  offset: int = 0,
58
41
  optimization_id: Optional[str] = None,
42
+ **kwargs,
59
43
  ) -> List[Solution]:
60
- """
61
- Get list of available optimizations.
62
-
63
- Parameters
64
- ----------
65
- timeframe: Optional[TimeframeEnum]
66
- Only return Solutions created within a specified timeframe. Default None.
67
- limit:
68
- Limit the number of Optimizations to be returned. Default value 10.
69
- offset:
70
- Offset the list of solutions by this amount. Default value 0.
71
- optimization_id: Optional[str]
72
- Show solutions for only this optimization id. Default None.
73
-
74
- Returns
75
- -------
76
- List[SolutionOut]
77
- List of SolutionOut instances.
78
- """
79
44
  params = {}
80
45
  if timeframe and timeframe != TimeframeEnum.all_time: # no value == all_time
81
46
  params["timeframe"] = timeframe.value
@@ -89,30 +54,14 @@ class SolutionsRepo(ISolutionsRepo):
89
54
 
90
55
  params["limit"] = str(limit)
91
56
  params["offset"] = str(offset)
92
- response = self._client.get(
93
- self._endpoint,
94
- params=params,
95
- )
57
+ response = self._client.get(self._endpoint, params=params, **kwargs)
96
58
 
97
59
  response.raise_for_status()
98
60
 
99
61
  return [Solution.model_validate(i) for i in response.json()]
100
62
 
101
- def delete(self, solution_id: str) -> None:
102
- """
103
- Delete one optimization by id.
104
-
105
- Parameters
106
- ----------
107
- solution_id: str
108
- Id of the optimization that should be deleted
109
-
110
- Returns
111
- -------
112
- """
113
- self._client.delete(
114
- f"{self._endpoint}/{solution_id}",
115
- )
63
+ def delete(self, solution_id: str, **kwargs) -> None:
64
+ self._client.delete(f"{self._endpoint}/{solution_id}", **kwargs)
116
65
 
117
66
  def create(
118
67
  self,
@@ -120,39 +69,16 @@ class SolutionsRepo(ISolutionsRepo):
120
69
  solver_name: str,
121
70
  provider: str,
122
71
  qpu_tokens: Optional[TokenProvider] = None,
123
- solver_parameters: Optional[Union[Dict, BaseModel]] = None,
72
+ solver_parameters: Optional[Union[Dict[str, Any], BaseModel]] = None,
124
73
  encryption_key: Optional[str] = None,
125
74
  name: Optional[str] = None,
75
+ fail_on_invalid_params: bool = True,
76
+ **kwargs,
126
77
  ) -> Solution:
127
- """
128
- Create a solution for an optimization
129
-
130
- Parameters
131
- ----------
132
- optimization_id: str
133
- The id of the optimization for which solution should be created
134
- solver_name: str
135
- The name of the solver to use.
136
- provider: str
137
- The name of the QPU provider to use.
138
- qpu_tokens: Optional[TokenProvider]
139
- The tokens to be used for the QPU.
140
- solver_parameters: Optional[Dict]
141
- Parameters to be passed to the solver.
142
- encryption_key: Optional[str]
143
- Encryption key to be used for encryption of QPU tokens.
144
- name: Optional[str]
145
- Default: None, The name of the solution to create.
146
-
147
- Returns
148
- -------
149
- SolutionOut
150
- Returns the location where the solution can be found once solving is complete.
151
- """
152
- if solver_parameters is None:
153
- solver_parameters = {}
154
78
  if qpu_tokens is not None:
155
- rest_qpu_tokens = RestAPITokenProvider.from_sdk_token_provider(qpu_tokens)
79
+ rest_qpu_tokens = RestAPITokenProvider.from_sdk_token_provider(
80
+ TokenProvider.model_validate(qpu_tokens)
81
+ )
156
82
  else:
157
83
  rest_qpu_tokens = None
158
84
  # try to retrieve qpu tokens from env variables
@@ -162,21 +88,21 @@ class SolutionsRepo(ISolutionsRepo):
162
88
  encryption_key = encryption_key or os.environ.get("LUNA_ENCRYPTION_KEY")
163
89
  if encryption_key is None:
164
90
  raise EncryptionNotSetException
91
+ params = SolutionsRepo.validate_solver_params(
92
+ solver_name, provider, solver_parameters, fail_on_invalid_params
93
+ )
94
+
165
95
  solution_in = SolutionIn(
166
96
  optimization=optimization_id,
167
97
  solver_name=solver_name,
168
98
  provider=provider,
169
- parameters=(
170
- solver_parameters.model_dump()
171
- if isinstance(solver_parameters, BaseModel)
172
- else solver_parameters
173
- ),
99
+ parameters=params,
174
100
  qpu_tokens=rest_qpu_tokens,
175
101
  encryption_key=encryption_key,
176
102
  name=name,
177
103
  )
178
104
  response = self._client.post(
179
- self._endpoint, content=solution_in.model_dump_json()
105
+ self._endpoint, content=solution_in.model_dump_json(), **kwargs
180
106
  )
181
107
  response.raise_for_status()
182
108
 
@@ -188,53 +114,19 @@ class SolutionsRepo(ISolutionsRepo):
188
114
  solver_name: str,
189
115
  provider: str,
190
116
  qpu_tokens: Optional[TokenProvider] = None,
191
- solver_parameters: Optional[Union[Dict, BaseModel]] = None,
117
+ solver_parameters: Optional[Union[Dict[str, Any], BaseModel]] = None,
192
118
  sleep_time_max: float = 60.0,
193
119
  sleep_time_increment: float = 5.0,
194
120
  sleep_time_initial: float = 5.0,
195
121
  encryption_key: Optional[str] = None,
196
122
  name: Optional[str] = None,
123
+ fail_on_invalid_params: bool = True,
124
+ **kwargs,
197
125
  ) -> Solution:
198
- """
199
- Create a solution for optimization. This method will block your code until the solution is ready.
200
- Depending on the problem size, this can take a long time.
201
-
202
- Parameters
203
- ----------
204
- optimization_id: str
205
- The id of the optimization for which solution should be created
206
- solver_name: str
207
- The name of the solver to use.
208
- provider: str
209
- The name of the provider to use.
210
-
211
- qpu_tokens: Optional[TokenProvider] = None
212
- The tokens to be used for the QPU.
213
- solver_parameters: Optional[Dict]
214
- Parameters to be passed to the solver.
215
-
216
- sleep_time_max: float
217
- Maximum time to sleep between requests.
218
- sleep_time_increment: float
219
- Increment of sleep time between requests. Initial sleep time will be
220
- sleep_time_initial: float
221
- Initial sleep time.
222
- encryption_key: Optional[str]
223
- Encryption key to be used for encryption of QPU tokens.
224
- name: Optional[str]
225
- Default: None, The name of the solution to create.
226
- Returns
227
- -------
228
- SolutionOut
229
- Returns the location where the solution can be found once solving is complete.
230
- """
231
126
  # First create the solution
232
-
233
- params: Optional[Union[Dict, BaseModel]] = None
234
- if solver_parameters is not None:
235
- params = solver_parameters
236
- if isinstance(solver_parameters, BaseModel):
237
- params = solver_parameters.dict()
127
+ params = SolutionsRepo.validate_solver_params(
128
+ solver_name, provider, solver_parameters, fail_on_invalid_params
129
+ )
238
130
 
239
131
  solution: Solution = self.create(
240
132
  optimization_id=optimization_id,
@@ -244,6 +136,7 @@ class SolutionsRepo(ISolutionsRepo):
244
136
  qpu_tokens=qpu_tokens,
245
137
  encryption_key=encryption_key,
246
138
  name=name,
139
+ **kwargs,
247
140
  )
248
141
  # times are in sec
249
142
 
@@ -271,44 +164,20 @@ class SolutionsRepo(ISolutionsRepo):
271
164
  if cur_sleep_time > sleep_time_max:
272
165
  cur_sleep_time = sleep_time_max
273
166
 
274
- solution = self.get(solution_id=solution.id)
167
+ solution = self.get(solution_id=solution.id, **kwargs)
275
168
 
276
169
  return solution
277
170
 
278
- def get_use_case_representation(self, solution_id: str) -> UseCaseRepresentation:
279
- """
280
- Get the use-case-specific representation of a solution.
281
-
282
- Parameters
283
- ----------
284
- solution_id: str
285
- Id of the solution that should be retrieved
286
-
287
- Returns
288
- -------
289
- UseCaseRepresentation
290
- The use-case-specific representation
291
- """
292
- response = self._client.get(f"{self._endpoint}/{solution_id}/representation")
171
+ def get_use_case_representation(
172
+ self, solution_id: str, **kwargs
173
+ ) -> UseCaseRepresentation:
174
+ response = self._client.get(
175
+ f"{self._endpoint}/{solution_id}/representation", **kwargs
176
+ )
293
177
  response.raise_for_status()
294
178
  return UseCaseRepresentation.model_validate_json(response.text)
295
179
 
296
180
  def get_best_result(self, solution: Solution) -> Optional[Result]:
297
- """
298
- Retrieves the best result from a solution.
299
-
300
- Parameters
301
- ----------
302
- solution : Solution
303
- The solution received via `solutions.get` or `solutions.get_all`.
304
-
305
- Returns
306
- -------
307
- Result | None
308
- The best result of the solution. If there are several best solutions with
309
- the same objective value, return only the first. If the solution results are
310
- not (yet) available or no the solution sense is `None`, return `None`.
311
- """
312
181
  if solution.results is None or solution.sense is None:
313
182
  return None
314
183
 
@@ -320,21 +189,6 @@ class SolutionsRepo(ISolutionsRepo):
320
189
  def get_best_use_case_result(
321
190
  self, use_case_representation: UseCaseRepresentation
322
191
  ) -> Optional[UseCaseResult]:
323
- """
324
- Retrieves the best result from a solution's use case representation.
325
-
326
- Parameters
327
- ----------
328
- use_case_representation : UseCaseRepresentation
329
- A solution's use case representation.
330
-
331
- Returns
332
- -------
333
- UseCaseResult | None
334
- The best result of the solution. If there are several best solutions with
335
- the same objective value, return only the first. If the solution results are
336
- not (yet) available or no the solution sense is `None`, return `None`.
337
- """
338
192
  if (
339
193
  use_case_representation.results is None
340
194
  or use_case_representation.sense is None
@@ -345,3 +199,77 @@ class SolutionsRepo(ISolutionsRepo):
345
199
  best_result = agg(use_case_representation.results, key=lambda x: x.obj_value)
346
200
 
347
201
  return best_result
202
+
203
+ @staticmethod
204
+ def validate_solver_params(
205
+ solver: str,
206
+ provider: str,
207
+ solver_parameter: Optional[Union[Dict[str, Any], BaseModel]],
208
+ fail_on_invalid_params: bool = True,
209
+ ) -> Dict[str, Any]:
210
+ """
211
+ This function checks if the params provided are valid for the provided solver
212
+ and provider.
213
+ If no parameter class was found, there will be no check.
214
+
215
+ Parameters
216
+ ----------
217
+ solver: str
218
+ The solver
219
+ provider: str
220
+ The provider
221
+ solver_parameter: Optional[Union[Dict[str, Any], BaseModel]]
222
+ The solver parameter
223
+ fail_on_invalid_params: bool
224
+ Default true.
225
+ If True, a ValueError will be raised, if the solver_parameter are invalid.
226
+ Otherwise, there will only a warning.
227
+
228
+ Returns
229
+ -------
230
+ Dict[str, Any]
231
+ Validated solver params
232
+
233
+ Raises
234
+ -------
235
+ ValidationError
236
+ If the object could not be validated.
237
+
238
+ """
239
+ if solver_parameter is None:
240
+ logging.info(
241
+ "You didn't provide any specific solver parameters, so we chose the "
242
+ "default ones for this solver."
243
+ )
244
+ return {}
245
+
246
+ params: Dict[str, Any]
247
+ if isinstance(solver_parameter, BaseModel):
248
+ params = solver_parameter.dict()
249
+ else:
250
+ params = solver_parameter
251
+
252
+ parameter_class: Optional[Type[BaseModel]] = get_parameter_by_solver(
253
+ solver, provider
254
+ )
255
+
256
+ if parameter_class is None:
257
+ logging.info(
258
+ f"SDK was not able to find a parameter for solver {solver} "
259
+ f"and provider {provider}."
260
+ )
261
+ return params
262
+
263
+ try:
264
+ parameter_class.model_validate(params)
265
+
266
+ except ValidationError as e:
267
+ if fail_on_invalid_params:
268
+ raise e
269
+ logging.warning(
270
+ "The validation for the provided solver parameter failed.\n"
271
+ f"Detected error:\n{e}\n"
272
+ "Since continue on error is enabled, no error was raised.\n"
273
+ "Solving with Luna can still fail due to the parameters."
274
+ )
275
+ return params
@@ -1,4 +1,4 @@
1
- from typing import Dict, Optional
1
+ from typing import Dict, Optional, Any
2
2
 
3
3
  from pydantic import BaseModel
4
4
 
@@ -9,7 +9,7 @@ class SolutionIn(BaseModel):
9
9
  optimization: str # id of the optimization
10
10
  solver_name: str
11
11
  provider: str
12
- parameters: Dict
12
+ parameters: Dict[str, Any]
13
13
  qpu_tokens: Optional[RestAPITokenProvider] = None
14
14
  encryption_key: str
15
15
  name: Optional[str] = None
@@ -1,13 +1,13 @@
1
- from datetime import datetime
2
1
  from typing import Any, Dict, Generic, List, Optional, TypeVar
3
2
 
4
- from pydantic import BaseModel, Extra
3
+ from pydantic import BaseModel, ConfigDict
5
4
 
6
5
  from luna_sdk.schemas.enums.optimization import InputType
7
6
  from luna_sdk.schemas.optimization_formats.bqm import BQMSchema
8
7
  from luna_sdk.schemas.optimization_formats.cqm import CQMSchema
9
8
  from luna_sdk.schemas.optimization_formats.lp import LPSchema
10
9
  from luna_sdk.schemas.pretty_base import PrettyBase
10
+ from luna_sdk.schemas.wrappers import PydanticDatetimeWrapper
11
11
 
12
12
 
13
13
  class Optimization(PrettyBase):
@@ -17,11 +17,11 @@ class Optimization(PrettyBase):
17
17
  ----------
18
18
  id: str
19
19
  Id of the optimization
20
- created_date: Optional[datetime]
20
+ created_date: Optional[DatetimeWrapper]
21
21
  Date when optimization was created
22
22
  created_by: Optional[str]
23
23
  Id of the user who created optimization
24
- modified_date: Optional[datetime]
24
+ modified_date: Optional[DatetimeWrapper]
25
25
  Date when optimization was modified
26
26
  modified_by: Optional[str]
27
27
  Id of the user who modified optimization
@@ -29,25 +29,23 @@ class Optimization(PrettyBase):
29
29
 
30
30
  id: str
31
31
  name: Optional[str] = None
32
- created_date: datetime
32
+ created_date: PydanticDatetimeWrapper
33
33
  created_by: str
34
- modified_date: Optional[datetime] = None
34
+ modified_date: Optional[PydanticDatetimeWrapper] = None
35
35
  modified_by: Optional[str] = None
36
36
  input_type: Optional[InputType] = None
37
37
  use_case_name: Optional[str] = None
38
38
  params: Optional[Dict[str, Any]] = None
39
39
 
40
- class Config:
41
- extra = Extra.ignore
42
- from_attributes = False
40
+ model_config = ConfigDict(extra="ignore", from_attributes=False)
43
41
 
44
42
 
45
43
  class OptimizationQubo(BaseModel):
46
44
  id: str
47
45
  name: Optional[str] = None
48
- created_date: datetime
46
+ created_date: PydanticDatetimeWrapper
49
47
  created_by: str
50
- modified_date: Optional[datetime] = None
48
+ modified_date: Optional[PydanticDatetimeWrapper] = None
51
49
  modified_by: Optional[str] = None
52
50
 
53
51
  matrix: List[List[float]]