• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

qiskit-community / qiskit-optimization / 16897576056

06 Aug 2025 01:46PM UTC coverage: 92.15% (-0.04%) from 92.191%
16897576056

push

github

web-flow
Deprecate lp file (#671)

* deprecate read_from_lp_file and write_to_lp_file

* update docs and tests

* reno

* try mode=w

* fix for windows

8 of 8 new or added lines in 2 files covered. (100.0%)

3 existing lines in 2 files now uncovered.

6010 of 6522 relevant lines covered (92.15%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.34
/qiskit_optimization/problems/quadratic_program.py
1
# This code is part of a Qiskit project.
2
#
3
# (C) Copyright IBM 2019, 2025.
4
#
5
# This code is licensed under the Apache License, Version 2.0. You may
6
# obtain a copy of this license in the LICENSE.txt file in the root directory
7
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
#
9
# Any modifications or derivative works of this code must retain this
10
# copyright notice, and modified files need to carry a notice indicating
11
# that they have been altered from the originals.
12

13
"""Quadratic Program."""
14

15
import logging
1✔
16
from collections.abc import Sequence
1✔
17
from enum import Enum
1✔
18
from math import isclose
1✔
19
from typing import Dict, List, Optional, Tuple, Union, cast
1✔
20
from warnings import warn
1✔
21

22
import numpy as np
1✔
23
from docplex.mp.model_reader import ModelReader
1✔
24
from numpy import ndarray
1✔
25
from qiskit.quantum_info import SparsePauliOp
1✔
26
from qiskit.quantum_info.operators.base_operator import BaseOperator
1✔
27
from qiskit.utils import deprecate_func
1✔
28
from scipy.sparse import spmatrix
1✔
29

30
import qiskit_optimization.optionals as _optionals
1✔
31

32
from ..exceptions import QiskitOptimizationError
1✔
33
from ..infinity import INFINITY
1✔
34
from .constraint import Constraint, ConstraintSense
1✔
35
from .linear_constraint import LinearConstraint
1✔
36
from .quadratic_constraint import QuadraticConstraint
1✔
37
from .quadratic_objective import QuadraticObjective
1✔
38
from .quadratic_program_element import QuadraticProgramElement
1✔
39
from .variable import Variable, VarType
1✔
40

41
logger = logging.getLogger(__name__)
1✔
42

43

44
class QuadraticProgramStatus(Enum):
1✔
45
    """Status of QuadraticProgram"""
46

47
    VALID = 0
1✔
48
    INFEASIBLE = 1
1✔
49

50

51
class QuadraticProgram:
1✔
52
    """Quadratically Constrained Quadratic Program representation.
53

54
    This representation supports inequality and equality constraints,
55
    as well as continuous, binary, and integer variables.
56
    """
57

58
    Status = QuadraticProgramStatus
1✔
59

60
    def __init__(self, name: str = "") -> None:
1✔
61
        """
62
        Args:
63
            name: The name of the quadratic program.
64
        """
65
        if not name.isprintable():
1✔
66
            warn("Problem name is not printable")
1✔
67
        self._name = ""
1✔
68
        self.name = name
1✔
69
        self._status = QuadraticProgram.Status.VALID
1✔
70

71
        self._variables: List[Variable] = []
1✔
72
        self._variables_index: Dict[str, int] = {}
1✔
73

74
        self._linear_constraints: List[LinearConstraint] = []
1✔
75
        self._linear_constraints_index: Dict[str, int] = {}
1✔
76

77
        self._quadratic_constraints: List[QuadraticConstraint] = []
1✔
78
        self._quadratic_constraints_index: Dict[str, int] = {}
1✔
79

80
        self._objective = QuadraticObjective(self)
1✔
81

82
    def __repr__(self) -> str:
1✔
83
        from ..translators.prettyprint import DEFAULT_TRUNCATE, expr2str
1✔
84

85
        objective = expr2str(
1✔
86
            constant=self._objective.constant,
87
            linear=self.objective.linear,
88
            quadratic=self._objective.quadratic,
89
            truncate=DEFAULT_TRUNCATE,
90
        )
91
        num_constraints = self.get_num_linear_constraints() + self.get_num_quadratic_constraints()
1✔
92
        return (
1✔
93
            f"<{self.__class__.__name__}: "
94
            f"{self.objective.sense.name.lower()} "
95
            f"{objective}, "
96
            f"{self.get_num_vars()} variables, "
97
            f"{num_constraints} constraints, "
98
            f"'{self._name}'>"
99
        )
100

101
    def __str__(self) -> str:
1✔
102
        num_constraints = self.get_num_linear_constraints() + self.get_num_quadratic_constraints()
1✔
103
        return (
1✔
104
            f"{str(self.objective)} "
105
            f"({self.get_num_vars()} variables, "
106
            f"{num_constraints} constraints, "
107
            f"'{self._name}')"
108
        )
109

110
    def clear(self) -> None:
1✔
111
        """Clears the quadratic program, i.e., deletes all variables, constraints, the
112
        objective function as well as the name.
113
        """
114
        self._name = ""
1✔
115
        self._status = QuadraticProgram.Status.VALID
1✔
116

117
        self._variables.clear()
1✔
118
        self._variables_index.clear()
1✔
119

120
        self._linear_constraints.clear()
1✔
121
        self._linear_constraints_index.clear()
1✔
122

123
        self._quadratic_constraints.clear()
1✔
124
        self._quadratic_constraints_index.clear()
1✔
125

126
        self._objective = QuadraticObjective(self)
1✔
127

128
    @property
1✔
129
    def name(self) -> str:
1✔
130
        """Returns the name of the quadratic program.
131

132
        Returns:
133
            The name of the quadratic program.
134
        """
135
        return self._name
1✔
136

137
    @name.setter
1✔
138
    def name(self, name: str) -> None:
1✔
139
        """Sets the name of the quadratic program.
140

141
        Args:
142
            name: The name of the quadratic program.
143
        """
144
        self._check_name(name, "Problem")
1✔
145
        self._name = name
1✔
146

147
    @property
1✔
148
    def status(self) -> QuadraticProgramStatus:
1✔
149
        """Status of the quadratic program.
150
        It can be infeasible due to variable substitution.
151

152
        Returns:
153
            The status of the quadratic program
154
        """
155
        return self._status
1✔
156

157
    @property
1✔
158
    def variables(self) -> List[Variable]:
1✔
159
        """Returns the list of variables of the quadratic program.
160

161
        Returns:
162
            List of variables.
163
        """
164
        return self._variables
1✔
165

166
    @property
1✔
167
    def variables_index(self) -> Dict[str, int]:
1✔
168
        """Returns the dictionary that maps the name of a variable to its index.
169

170
        Returns:
171
            The variable index dictionary.
172
        """
173
        return self._variables_index
1✔
174

175
    def _add_variable(
1✔
176
        self,
177
        lowerbound: Union[float, int],
178
        upperbound: Union[float, int],
179
        vartype: VarType,
180
        name: Optional[str],
181
    ) -> Variable:
182
        if not name:
1✔
183
            name = "x"
1✔
184
            key_format = "{}"
1✔
185
        else:
186
            key_format = ""
1✔
187
        return self._add_variables(1, lowerbound, upperbound, vartype, name, key_format)[1][0]
1✔
188

189
    def _add_variables(  # pylint: disable=too-many-positional-arguments
1✔
190
        self,
191
        keys: Union[int, Sequence],
192
        lowerbound: Union[float, int],
193
        upperbound: Union[float, int],
194
        vartype: VarType,
195
        name: Optional[str],
196
        key_format: str,
197
    ) -> Tuple[List[str], List[Variable]]:
198
        if isinstance(keys, int) and keys < 1:
1✔
199
            raise QiskitOptimizationError(f"Cannot create non-positive number of variables: {keys}")
1✔
200
        if not name:
1✔
201
            name = "x"
1✔
202
        if "{{}}" in key_format:
1✔
203
            raise QiskitOptimizationError(
1✔
204
                f"Formatter cannot contain nested substitutions: {key_format}"
205
            )
206
        if key_format.count("{}") > 1:
1✔
207
            raise QiskitOptimizationError(
1✔
208
                f"Formatter cannot contain more than one substitution: {key_format}"
209
            )
210

211
        def _find_name(name, key_format, k):
1✔
212
            prev = None
1✔
213
            while True:
214
                new_name = name + key_format.format(k)
1✔
215
                if new_name == prev:
1✔
216
                    raise QiskitOptimizationError(f"Variable name already exists: {new_name}")
1✔
217
                if new_name in self._variables_index:
1✔
218
                    k += 1
1✔
219
                    prev = new_name
1✔
220
                else:
221
                    break
×
222
            return new_name, k + 1
1✔
223

224
        names = []
1✔
225
        variables = []
1✔
226
        k = self.get_num_vars()
1✔
227
        lst = keys if isinstance(keys, Sequence) else range(keys)
1✔
228
        for key in lst:
1✔
229
            if isinstance(keys, Sequence):
1✔
230
                indexed_name = name + key_format.format(key)
1✔
231
            else:
232
                indexed_name, k = _find_name(name, key_format, k)
1✔
233
            if indexed_name in self._variables_index:
1✔
234
                raise QiskitOptimizationError(f"Variable name already exists: {indexed_name}")
1✔
235
            self._check_name(indexed_name, "Variable")
1✔
236
            names.append(indexed_name)
1✔
237
            self._variables_index[indexed_name] = self.get_num_vars()
1✔
238
            variable = Variable(self, indexed_name, lowerbound, upperbound, vartype)
1✔
239
            self._variables.append(variable)
1✔
240
            variables.append(variable)
1✔
241
        return names, variables
1✔
242

243
    def _var_dict(  # pylint: disable=too-many-positional-arguments
1✔
244
        self,
245
        keys: Union[int, Sequence],
246
        lowerbound: Union[float, int],
247
        upperbound: Union[float, int],
248
        vartype: VarType,
249
        name: Optional[str],
250
        key_format: str,
251
    ) -> Dict[str, Variable]:
252
        """
253
        Adds a positive number of variables to the variable list and index and returns a
254
        dictionary mapping the variable names to their instances. If 'key_format' is present,
255
        the next 'var_count' available indices are substituted into 'key_format' and appended
256
        to 'name'.
257

258
        Args:
259
            lowerbound: The lower bound of the variable(s).
260
            upperbound: The upper bound of the variable(s).
261
            vartype: The type of the variable(s).
262
            name: The name(s) of the variable(s).
263
            key_format: The format used to name/index the variable(s).
264
            keys: If keys: int, it is interpreted as the number of variables to construct.
265
                  Otherwise, the elements of the sequence are converted to strings via 'str' and
266
                  substituted into `key_format`.
267

268
        Returns:
269
            A dictionary mapping the variable names to variable instances.
270

271
        Raises:
272
            QiskitOptimizationError: if the variable name is already taken.
273
            QiskitOptimizationError: if less than one variable instantiation is attempted.
274
            QiskitOptimizationError: if `key_format` has more than one substitution or a
275
                                     nested substitution.
276
        """
277
        return dict(
1✔
278
            zip(*self._add_variables(keys, lowerbound, upperbound, vartype, name, key_format))
279
        )
280

281
    def _var_list(  # pylint: disable=too-many-positional-arguments
1✔
282
        self,
283
        keys: Union[int, Sequence],
284
        lowerbound: Union[float, int],
285
        upperbound: Union[float, int],
286
        vartype: VarType,
287
        name: Optional[str],
288
        key_format: str,
289
    ) -> List[Variable]:
290
        """
291
        Adds a positive number of variables to the variable list and index and returns a
292
        list of variable instances.
293

294
        Args:
295
            lowerbound: The lower bound of the variable(s).
296
            upperbound: The upper bound of the variable(s).
297
            vartype: The type of the variable(s).
298
            name: The name(s) of the variable(s).
299
            key_format: The format used to name/index the variable(s).
300
            keys: If keys: int, it is interpreted as the number of variables to construct.
301
                  Otherwise, the elements of the sequence are converted to strings via 'str' and
302
                  substituted into `key_format`.
303

304
        Returns:
305
            A dictionary mapping the variable names to variable instances.
306

307
        Raises:
308
            QiskitOptimizationError: if the variable name is already taken.
309
            QiskitOptimizationError: if less than one variable instantiation is attempted.
310
            QiskitOptimizationError: if `key_format` has more than one substitution or a
311
                                     nested substitution.
312
        """
313
        return self._add_variables(keys, lowerbound, upperbound, vartype, name, key_format)[1]
1✔
314

315
    def continuous_var(
1✔
316
        self,
317
        lowerbound: Union[float, int] = 0,
318
        upperbound: Union[float, int] = INFINITY,
319
        name: Optional[str] = None,
320
    ) -> Variable:
321
        """Adds a continuous variable to the quadratic program.
322

323
        Args:
324
            lowerbound: The lowerbound of the variable.
325
            upperbound: The upperbound of the variable.
326
            name: The name of the variable.
327
                If it's ``None`` or empty ``""``, the default name, e.g., ``x0``, is used.
328

329
        Returns:
330
            The added variable.
331

332
        Raises:
333
            QiskitOptimizationError: if the variable name is already occupied.
334
        """
335
        return self._add_variable(lowerbound, upperbound, Variable.Type.CONTINUOUS, name)
1✔
336

337
    def continuous_var_dict(  # pylint: disable=too-many-positional-arguments
1✔
338
        self,
339
        keys: Union[int, Sequence],
340
        lowerbound: Union[float, int] = 0,
341
        upperbound: Union[float, int] = INFINITY,
342
        name: Optional[str] = None,
343
        key_format: str = "{}",
344
    ) -> Dict[str, Variable]:
345
        """
346
        Uses 'var_dict' to construct a dictionary of continuous variables
347

348
        Args:
349
            lowerbound: The lower bound of the variable(s).
350
            upperbound: The upper bound of the variable(s).
351
            name: The name(s) of the variable(s).
352
                If it's ``None`` or empty ``""``, the default name, e.g., ``x0``, is used.
353
            key_format: The format used to name/index the variable(s).
354
            keys: If keys: int, it is interpreted as the number of variables to construct.
355
                  Otherwise, the elements of the sequence are converted to strings via 'str' and
356
                  substituted into `key_format`.
357

358
        Returns:
359
            A dictionary mapping the variable names to variable instances.
360

361
        Raises:
362
            QiskitOptimizationError: if the variable name is already taken.
363
            QiskitOptimizationError: if less than one variable instantiation is attempted.
364
            QiskitOptimizationError: if `key_format` has more than one substitution or a
365
                                     nested substitution.
366
        """
367
        return self._var_dict(
1✔
368
            keys=keys,
369
            lowerbound=lowerbound,
370
            upperbound=upperbound,
371
            vartype=Variable.Type.CONTINUOUS,
372
            name=name,
373
            key_format=key_format,
374
        )
375

376
    def continuous_var_list(  # pylint: disable=too-many-positional-arguments
1✔
377
        self,
378
        keys: Union[int, Sequence],
379
        lowerbound: Union[float, int] = 0,
380
        upperbound: Union[float, int] = INFINITY,
381
        name: Optional[str] = None,
382
        key_format: str = "{}",
383
    ) -> List[Variable]:
384
        """
385
        Uses 'var_list' to construct a list of continuous variables
386

387
        Args:
388
            lowerbound: The lower bound of the variable(s).
389
            upperbound: The upper bound of the variable(s).
390
            name: The name(s) of the variable(s).
391
                If it's ``None`` or empty ``""``, the default name, e.g., ``x0``, is used.
392
            key_format: The format used to name/index the variable(s).
393
            keys: If keys: int, it is interpreted as the number of variables to construct.
394
                  Otherwise, the elements of the sequence are converted to strings via 'str' and
395
                  substituted into `key_format`.
396

397
        Returns:
398
            A list of variable instances.
399

400
        Raises:
401
            QiskitOptimizationError: if the variable name is already taken.
402
            QiskitOptimizationError: if less than one variable instantiation is attempted.
403
            QiskitOptimizationError: if `key_format` has more than one substitution or a
404
                                     nested substitution.
405
        """
406
        return self._var_list(
1✔
407
            keys, lowerbound, upperbound, Variable.Type.CONTINUOUS, name, key_format
408
        )
409

410
    def binary_var(self, name: Optional[str] = None) -> Variable:
1✔
411
        """Adds a binary variable to the quadratic program.
412

413
        Args:
414
            name: The name of the variable.
415
                If it's ``None`` or empty ``""``, the default name, e.g., ``x0``, is used.
416

417
        Returns:
418
            The added variable.
419

420
        Raises:
421
            QiskitOptimizationError: if the variable name is already occupied.
422
        """
423
        return self._add_variable(0, 1, Variable.Type.BINARY, name)
1✔
424

425
    def binary_var_dict(
1✔
426
        self,
427
        keys: Union[int, Sequence],
428
        name: Optional[str] = None,
429
        key_format: str = "{}",
430
    ) -> Dict[str, Variable]:
431
        """
432
        Uses 'var_dict' to construct a dictionary of binary variables
433

434
        Args:
435
            name: The name(s) of the variable(s).
436
                If it's ``None`` or empty ``""``, the default name, e.g., ``x0``, is used.
437
            key_format: The format used to name/index the variable(s).
438
            keys: If keys: int, it is interpreted as the number of variables to construct.
439
                  Otherwise, the elements of the sequence are converted to strings via 'str' and
440
                  substituted into `key_format`.
441

442
        Returns:
443
            A dictionary mapping the variable names to variable instances.
444

445
        Raises:
446
            QiskitOptimizationError: if the variable name is already taken.
447
            QiskitOptimizationError: if less than one variable instantiation is attempted.
448
            QiskitOptimizationError: if `key_format` has more than one substitution or a
449
                                     nested substitution.
450
        """
451
        return self._var_dict(
1✔
452
            keys=keys,
453
            lowerbound=0,
454
            upperbound=1,
455
            vartype=Variable.Type.BINARY,
456
            name=name,
457
            key_format=key_format,
458
        )
459

460
    def binary_var_list(
1✔
461
        self,
462
        keys: Union[int, Sequence],
463
        name: Optional[str] = None,
464
        key_format: str = "{}",
465
    ) -> List[Variable]:
466
        """
467
        Uses 'var_list' to construct a list of binary variables
468

469
        Args:
470
            name: The name(s) of the variable(s).
471
                If it's ``None`` or empty ``""``, the default name, e.g., ``x0``, is used.
472
            key_format: The format used to name/index the variable(s).
473
            keys: If keys: int, it is interpreted as the number of variables to construct.
474
                  Otherwise, the elements of the sequence are converted to strings via 'str' and
475
                  substituted into `key_format`.
476

477
        Returns:
478
            A list of variable instances.
479

480
        Raises:
481
            QiskitOptimizationError: if the variable name is already taken.
482
            QiskitOptimizationError: if less than one variable instantiation is attempted.
483
            QiskitOptimizationError: if `key_format` has more than one substitution or a
484
                                     nested substitution.
485
        """
486
        return self._var_list(keys, 0, 1, Variable.Type.BINARY, name, key_format)
1✔
487

488
    def integer_var(
1✔
489
        self,
490
        lowerbound: Union[float, int] = 0,
491
        upperbound: Union[float, int] = INFINITY,
492
        name: Optional[str] = None,
493
    ) -> Variable:
494
        """Adds an integer variable to the quadratic program.
495

496
        Args:
497
            lowerbound: The lowerbound of the variable.
498
            upperbound: The upperbound of the variable.
499
            name: The name of the variable.
500
                If it's ``None`` or empty ``""``, the default name, e.g., ``x0``, is used.
501

502
        Returns:
503
            The added variable.
504

505
        Raises:
506
            QiskitOptimizationError: if the variable name is already occupied.
507
        """
508
        return self._add_variable(lowerbound, upperbound, Variable.Type.INTEGER, name)
1✔
509

510
    def integer_var_dict(  # pylint: disable=too-many-positional-arguments
1✔
511
        self,
512
        keys: Union[int, Sequence],
513
        lowerbound: Union[float, int] = 0,
514
        upperbound: Union[float, int] = INFINITY,
515
        name: Optional[str] = None,
516
        key_format: str = "{}",
517
    ) -> Dict[str, Variable]:
518
        """
519
        Uses 'var_dict' to construct a dictionary of integer variables
520

521
        Args:
522
            lowerbound: The lower bound of the variable(s).
523
            upperbound: The upper bound of the variable(s).
524
            name: The name(s) of the variable(s).
525
                If it's ``None`` or empty ``""``, the default name, e.g., ``x0``, is used.
526
            key_format: The format used to name/index the variable(s).
527
            keys: If keys: int, it is interpreted as the number of variables to construct.
528
                  Otherwise, the elements of the sequence are converted to strings via 'str' and
529
                  substituted into `key_format`.
530

531
        Returns:
532
            A dictionary mapping the variable names to variable instances.
533

534
        Raises:
535
            QiskitOptimizationError: if the variable name is already taken.
536
            QiskitOptimizationError: if less than one variable instantiation is attempted.
537
            QiskitOptimizationError: if `key_format` has more than one substitution or a
538
                                     nested substitution.
539
        """
540
        return self._var_dict(
1✔
541
            keys=keys,
542
            lowerbound=lowerbound,
543
            upperbound=upperbound,
544
            vartype=Variable.Type.INTEGER,
545
            name=name,
546
            key_format=key_format,
547
        )
548

549
    def integer_var_list(  # pylint: disable=too-many-positional-arguments
1✔
550
        self,
551
        keys: Union[int, Sequence],
552
        lowerbound: Union[float, int] = 0,
553
        upperbound: Union[float, int] = INFINITY,
554
        name: Optional[str] = None,
555
        key_format: str = "{}",
556
    ) -> List[Variable]:
557
        """
558
        Uses 'var_list' to construct a list of integer variables
559

560
        Args:
561
            lowerbound: The lower bound of the variable(s).
562
            upperbound: The upper bound of the variable(s).
563
            name: The name(s) of the variable(s).
564
                If it's ``None`` or empty ``""``, the default name, e.g., ``x0``, is used.
565
            key_format: The format used to name/index the variable(s).
566
            keys: If keys: int, it is interpreted as the number of variables to construct.
567
                  Otherwise, the elements of the sequence are converted to strings via 'str' and
568
                  substituted into `key_format`.
569

570
        Returns:
571
            A list of variable instances.
572

573
        Raises:
574
            QiskitOptimizationError: if the variable name is already taken.
575
            QiskitOptimizationError: if less than one variable instantiation is attempted.
576
            QiskitOptimizationError: if `key_format` has more than one substitution or a
577
                                     nested substitution.
578
        """
579
        return self._var_list(keys, lowerbound, upperbound, Variable.Type.INTEGER, name, key_format)
1✔
580

581
    def get_variable(self, i: Union[int, str]) -> Variable:
1✔
582
        """Returns a variable for a given name or index.
583

584
        Args:
585
            i: the index or name of the variable.
586

587
        Returns:
588
            The corresponding variable.
589
        """
590
        if isinstance(i, (int, np.integer)):
1✔
591
            return self.variables[i]
1✔
592
        else:
593
            return self.variables[self._variables_index[i]]
1✔
594

595
    def get_num_vars(self, vartype: Optional[VarType] = None) -> int:
1✔
596
        """Returns the total number of variables or the number of variables of the specified type.
597

598
        Args:
599
            vartype: The type to be filtered on. All variables are counted if None.
600

601
        Returns:
602
            The total number of variables.
603
        """
604
        if vartype:
1✔
605
            return sum(variable.vartype == vartype for variable in self._variables)
1✔
606
        else:
607
            return len(self._variables)
1✔
608

609
    def get_num_continuous_vars(self) -> int:
1✔
610
        """Returns the total number of continuous variables.
611

612
        Returns:
613
            The total number of continuous variables.
614
        """
615
        return self.get_num_vars(Variable.Type.CONTINUOUS)
1✔
616

617
    def get_num_binary_vars(self) -> int:
1✔
618
        """Returns the total number of binary variables.
619

620
        Returns:
621
            The total number of binary variables.
622
        """
623
        return self.get_num_vars(Variable.Type.BINARY)
1✔
624

625
    def get_num_integer_vars(self) -> int:
1✔
626
        """Returns the total number of integer variables.
627

628
        Returns:
629
            The total number of integer variables.
630
        """
631
        return self.get_num_vars(Variable.Type.INTEGER)
1✔
632

633
    @property
1✔
634
    def linear_constraints(self) -> List[LinearConstraint]:
1✔
635
        """Returns the list of linear constraints of the quadratic program.
636

637
        Returns:
638
            List of linear constraints.
639
        """
640
        return self._linear_constraints
1✔
641

642
    @property
1✔
643
    def linear_constraints_index(self) -> Dict[str, int]:
1✔
644
        """Returns the dictionary that maps the name of a linear constraint to its index.
645

646
        Returns:
647
            The linear constraint index dictionary.
648
        """
649
        return self._linear_constraints_index
1✔
650

651
    def linear_constraint(
1✔
652
        self,
653
        linear: Union[ndarray, spmatrix, List[float], Dict[Union[int, str], float]] = None,
654
        sense: Union[str, ConstraintSense] = "<=",
655
        rhs: float = 0.0,
656
        name: Optional[str] = None,
657
    ) -> LinearConstraint:
658
        """Adds a linear equality constraint to the quadratic program of the form:
659
            ``(linear * x) sense rhs``.
660

661
        Args:
662
            linear: The linear coefficients of the left-hand side of the constraint.
663
            sense: The sense of the constraint,
664

665
              - ``==``, ``=``, ``E``, and ``EQ`` denote 'equal to'.
666
              - ``>=``, ``>``, ``G``, and ``GE`` denote 'greater-than-or-equal-to'.
667
              - ``<=``, ``<``, ``L``, and ``LE`` denote 'less-than-or-equal-to'.
668

669
            rhs: The right-hand side of the constraint.
670
            name: The name of the constraint.
671
                If it's ``None`` or empty ``""``, the default name, e.g., ``c0``, is used.
672

673
        Returns:
674
            The added constraint.
675

676
        Raises:
677
            QiskitOptimizationError: if the constraint name already exists or the sense is not
678
                valid.
679
        """
680
        if name:
1✔
681
            if name in self.linear_constraints_index:
1✔
682
                raise QiskitOptimizationError(f"Linear constraint's name already exists: {name}")
1✔
683
            self._check_name(name, "Linear constraint")
1✔
684
        else:
685
            k = self.get_num_linear_constraints()
1✔
686
            while f"c{k}" in self.linear_constraints_index:
1✔
687
                k += 1
1✔
688
            name = f"c{k}"
1✔
689
        self.linear_constraints_index[name] = len(self.linear_constraints)
1✔
690
        if linear is None:
1✔
691
            linear = {}
1✔
692
        constraint = LinearConstraint(self, name, linear, Constraint.Sense.convert(sense), rhs)
1✔
693
        self.linear_constraints.append(constraint)
1✔
694
        return constraint
1✔
695

696
    def get_linear_constraint(self, i: Union[int, str]) -> LinearConstraint:
1✔
697
        """Returns a linear constraint for a given name or index.
698

699
        Args:
700
            i: the index or name of the constraint.
701

702
        Returns:
703
            The corresponding constraint.
704

705
        Raises:
706
            IndexError: if the index is out of the list size
707
            KeyError: if the name does not exist
708
        """
709
        if isinstance(i, int):
1✔
710
            return self._linear_constraints[i]
1✔
711
        else:
712
            return self._linear_constraints[self._linear_constraints_index[i]]
1✔
713

714
    def get_num_linear_constraints(self) -> int:
1✔
715
        """Returns the number of linear constraints.
716

717
        Returns:
718
            The number of linear constraints.
719
        """
720
        return len(self._linear_constraints)
1✔
721

722
    @property
1✔
723
    def quadratic_constraints(self) -> List[QuadraticConstraint]:
1✔
724
        """Returns the list of quadratic constraints of the quadratic program.
725

726
        Returns:
727
            List of quadratic constraints.
728
        """
729
        return self._quadratic_constraints
1✔
730

731
    @property
1✔
732
    def quadratic_constraints_index(self) -> Dict[str, int]:
1✔
733
        """Returns the dictionary that maps the name of a quadratic constraint to its index.
734

735
        Returns:
736
            The quadratic constraint index dictionary.
737
        """
738
        return self._quadratic_constraints_index
1✔
739

740
    def quadratic_constraint(  # pylint: disable=too-many-positional-arguments
1✔
741
        self,
742
        linear: Union[ndarray, spmatrix, List[float], Dict[Union[int, str], float]] = None,
743
        quadratic: Union[
744
            ndarray,
745
            spmatrix,
746
            List[List[float]],
747
            Dict[Tuple[Union[int, str], Union[int, str]], float],
748
        ] = None,
749
        sense: Union[str, ConstraintSense] = "<=",
750
        rhs: float = 0.0,
751
        name: Optional[str] = None,
752
    ) -> QuadraticConstraint:
753
        """Adds a quadratic equality constraint to the quadratic program of the form:
754
            ``(x * quadratic * x + linear * x) sense rhs``.
755

756
        Args:
757
            linear: The linear coefficients of the constraint.
758
            quadratic: The quadratic coefficients of the constraint.
759
            sense: The sense of the constraint,
760

761
              - ``==``, ``=``, ``E``, and ``EQ`` denote 'equal to'.
762
              - ``>=``, ``>``, ``G``, and ``GE`` denote 'greater-than-or-equal-to'.
763
              - ``<=``, ``<``, ``L``, and ``LE`` denote 'less-than-or-equal-to'.
764

765
            rhs: The right-hand side of the constraint.
766
            name: The name of the constraint.
767
                If it's ``None`` or empty ``""``, the default name, e.g., ``q0``, is used.
768

769
        Returns:
770
            The added constraint.
771

772
        Raises:
773
            QiskitOptimizationError: if the constraint name already exists.
774
        """
775
        if name:
1✔
776
            if name in self.quadratic_constraints_index:
1✔
777
                raise QiskitOptimizationError(f"Quadratic constraint name already exists: {name}")
1✔
778
            self._check_name(name, "Quadratic constraint")
1✔
779
        else:
780
            k = self.get_num_quadratic_constraints()
1✔
781
            while f"q{k}" in self.quadratic_constraints_index:
1✔
782
                k += 1
×
783
            name = f"q{k}"
1✔
784
        self.quadratic_constraints_index[name] = len(self.quadratic_constraints)
1✔
785
        if linear is None:
1✔
786
            linear = {}
1✔
787
        if quadratic is None:
1✔
788
            quadratic = {}
1✔
789
        constraint = QuadraticConstraint(
1✔
790
            self, name, linear, quadratic, Constraint.Sense.convert(sense), rhs
791
        )
792
        self.quadratic_constraints.append(constraint)
1✔
793
        return constraint
1✔
794

795
    def get_quadratic_constraint(self, i: Union[int, str]) -> QuadraticConstraint:
1✔
796
        """Returns a quadratic constraint for a given name or index.
797

798
        Args:
799
            i: the index or name of the constraint.
800

801
        Returns:
802
            The corresponding constraint.
803

804
        Raises:
805
            IndexError: if the index is out of the list size
806
            KeyError: if the name does not exist
807
        """
808
        if isinstance(i, int):
1✔
809
            return self._quadratic_constraints[i]
1✔
810
        else:
811
            return self._quadratic_constraints[self._quadratic_constraints_index[i]]
1✔
812

813
    def get_num_quadratic_constraints(self) -> int:
1✔
814
        """Returns the number of quadratic constraints.
815

816
        Returns:
817
            The number of quadratic constraints.
818
        """
819
        return len(self._quadratic_constraints)
1✔
820

821
    def remove_linear_constraint(self, i: Union[str, int]) -> None:
1✔
822
        """Remove a linear constraint
823

824
        Args:
825
            i: an index or a name of a linear constraint
826

827
        Raises:
828
            KeyError: if name does not exist
829
            IndexError: if index is out of range
830
        """
831
        if isinstance(i, str):
1✔
832
            i = self._linear_constraints_index[i]
1✔
833
        del self._linear_constraints[i]
1✔
834
        self._linear_constraints_index = {
1✔
835
            cst.name: j for j, cst in enumerate(self._linear_constraints)
836
        }
837

838
    def remove_quadratic_constraint(self, i: Union[str, int]) -> None:
1✔
839
        """Remove a quadratic constraint
840

841
        Args:
842
            i: an index or a name of a quadratic constraint
843

844
        Raises:
845
            KeyError: if name does not exist
846
            IndexError: if index is out of range
847
        """
848
        if isinstance(i, str):
1✔
849
            i = self._quadratic_constraints_index[i]
1✔
850
        del self._quadratic_constraints[i]
1✔
851
        self._quadratic_constraints_index = {
1✔
852
            cst.name: j for j, cst in enumerate(self._quadratic_constraints)
853
        }
854

855
    @property
1✔
856
    def objective(self) -> QuadraticObjective:
1✔
857
        """Returns the quadratic objective.
858

859
        Returns:
860
            The quadratic objective.
861
        """
862
        return self._objective
1✔
863

864
    def minimize(
1✔
865
        self,
866
        constant: float = 0.0,
867
        linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] = None,
868
        quadratic: Union[
869
            ndarray,
870
            spmatrix,
871
            List[List[float]],
872
            Dict[Tuple[Union[int, str], Union[int, str]], float],
873
        ] = None,
874
    ) -> None:
875
        """Sets a quadratic objective to be minimized.
876

877
        Args:
878
            constant: the constant offset of the objective.
879
            linear: the coefficients of the linear part of the objective.
880
            quadratic: the coefficients of the quadratic part of the objective.
881

882
        Returns:
883
            The created quadratic objective.
884
        """
885
        self._objective = QuadraticObjective(
1✔
886
            self, constant, linear, quadratic, QuadraticObjective.Sense.MINIMIZE
887
        )
888

889
    def maximize(
1✔
890
        self,
891
        constant: float = 0.0,
892
        linear: Union[ndarray, spmatrix, List[float], Dict[Union[str, int], float]] = None,
893
        quadratic: Union[
894
            ndarray,
895
            spmatrix,
896
            List[List[float]],
897
            Dict[Tuple[Union[int, str], Union[int, str]], float],
898
        ] = None,
899
    ) -> None:
900
        """Sets a quadratic objective to be maximized.
901

902
        Args:
903
            constant: the constant offset of the objective.
904
            linear: the coefficients of the linear part of the objective.
905
            quadratic: the coefficients of the quadratic part of the objective.
906

907
        Returns:
908
            The created quadratic objective.
909
        """
910
        self._objective = QuadraticObjective(
1✔
911
            self, constant, linear, quadratic, QuadraticObjective.Sense.MAXIMIZE
912
        )
913

914
    def _copy_from(self, other: "QuadraticProgram", include_name: bool) -> None:
1✔
915
        """Copy another QuadraticProgram to this updating QuadraticProgramElement
916

917
        Note: this breaks the consistency of `other`. You cannot use `other` after the copy.
918

919
        Args:
920
            other: The quadratic program to be copied from.
921
            include_name: Whether this method copies the problem name or not.
922
        """
923
        for attr, val in vars(other).items():
1✔
924
            if attr == "_name" and not include_name:
1✔
925
                continue
1✔
926
            if isinstance(val, QuadraticProgramElement):
1✔
927
                val.quadratic_program = self
1✔
928
            if isinstance(val, list):
1✔
929
                for elem in val:
1✔
930
                    if isinstance(elem, QuadraticProgramElement):
1✔
931
                        elem.quadratic_program = self
1✔
932
            setattr(self, attr, val)
1✔
933

934
    @deprecate_func(since="0.7.0", additional_msg="Use prettyprint instead.")
1✔
935
    def export_as_lp_string(self) -> str:
1✔
936
        """Returns the quadratic program as a string of LP format.
937

938
        Returns:
939
            A string representing the quadratic program.
940
        """
941
        # pylint: disable=cyclic-import
UNCOV
942
        from ..translators.docplex_mp import to_docplex_mp
×
943

UNCOV
944
        return to_docplex_mp(self).export_as_lp_string()
×
945

946
    @_optionals.HAS_CPLEX.require_in_call
1✔
947
    @deprecate_func(since="0.7.0", additional_msg="Use from_docplex_mp or from_gurobipy instead.")
1✔
948
    def read_from_lp_file(self, filename: str) -> None:
1✔
949
        """Loads the quadratic program from a LP file.
950

951
        Args:
952
            filename: The filename of the file to be loaded.
953

954
        Raises:
955
            FileNotFoundError: If the file does not exist.
956

957
        Note:
958
            This method requires CPLEX to be installed and present in ``PYTHONPATH``.
959
        """
960

961
        def _parse_problem_name(filename: str) -> str:
1✔
962
            # Because docplex model reader uses the base name as model name,
963
            # we parse the model name in the LP file manually.
964
            # https://ibmdecisionoptimization.github.io/docplex-doc/mp/docplex.mp.model_reader.html
965
            prefix = "\\Problem name:"
1✔
966
            model_name = ""
1✔
967
            with open(filename, encoding="utf8") as file:
1✔
968
                for line in file:
1✔
969
                    if line.startswith(prefix):
1✔
970
                        model_name = line[len(prefix) :].strip()
1✔
971
                    if not line.startswith("\\"):
1✔
972
                        break
1✔
973
            return model_name
1✔
974

975
        # pylint: disable=cyclic-import
976
        from ..translators.docplex_mp import from_docplex_mp
1✔
977

978
        model = ModelReader().read(filename, model_name=_parse_problem_name(filename))
1✔
979
        other = from_docplex_mp(model)
1✔
980
        self._copy_from(other, include_name=True)
1✔
981

982
    @deprecate_func(since="0.7.0", additional_msg="Use to_docplex_mp or to_gurobipy instead.")
1✔
983
    def write_to_lp_file(self, filename: str) -> None:
1✔
984
        """Writes the quadratic program to an LP file.
985

986
        Args:
987
            filename: The filename of the file the model is written to.
988
              If filename is a directory, file name 'my_problem.lp' is appended.
989
              If filename does not end with '.lp', suffix '.lp' is appended.
990

991
        Raises:
992
            OSError: If this cannot open a file.
993
            DOcplexException: If filename is an empty string
994
        """
995
        # pylint: disable=cyclic-import
996
        from ..translators.docplex_mp import to_docplex_mp
1✔
997

998
        mdl = to_docplex_mp(self)
1✔
999
        mdl.export_as_lp(filename)
1✔
1000

1001
    def substitute_variables(
1✔
1002
        self,
1003
        constants: Optional[Dict[Union[str, int], float]] = None,
1004
        variables: Optional[Dict[Union[str, int], Tuple[Union[str, int], float]]] = None,
1005
    ) -> "QuadraticProgram":
1006
        """Substitutes variables with constants or other variables.
1007

1008
        Args:
1009
            constants: replace variable by constant
1010
                e.g., ``{'x': 2}`` means ``x`` is substituted with 2
1011

1012
            variables: replace variables by weighted other variable
1013
                need to copy everything using name reference to make sure that indices are matched
1014
                correctly. The lower and upper bounds are updated accordingly.
1015
                e.g., ``{'x': ('y', 2)}`` means ``x`` is substituted with ``y * 2``
1016

1017
        Returns:
1018
            An optimization problem by substituting variables with constants or other variables.
1019
            If the substitution is valid, ``QuadraticProgram.status`` is still
1020
            ``QuadraticProgram.Status.VALID``.
1021
            Otherwise, it gets ``QuadraticProgram.Status.INFEASIBLE``.
1022

1023
        Raises:
1024
            QiskitOptimizationError: if the substitution is invalid as follows.
1025

1026
                - Same variable is substituted multiple times.
1027
                - Coefficient of variable substitution is zero.
1028
        """
1029
        # pylint: disable=cyclic-import
1030
        from .substitute_variables import substitute_variables
1✔
1031

1032
        return substitute_variables(self, constants, variables)
1✔
1033

1034
    def to_ising(self) -> Tuple[SparsePauliOp, float]:
1✔
1035
        """Return the Ising Hamiltonian of this problem.
1036

1037
        Variables are mapped to qubits in the same order, i.e.,
1038
        i-th variable is mapped to i-th qubit.
1039
        See https://github.com/Qiskit/qiskit-terra/issues/1148 for details.
1040

1041
        Returns:
1042
            qubit_op: The qubit operator for the problem
1043
            offset: The constant value in the Ising Hamiltonian.
1044

1045
        Raises:
1046
            QiskitOptimizationError: If a variable type is not binary.
1047
            QiskitOptimizationError: If constraints exist in the problem.
1048
        """
1049
        # pylint: disable=cyclic-import
1050
        from ..translators.ising import to_ising
1✔
1051

1052
        return to_ising(self)
1✔
1053

1054
    def from_ising(
1✔
1055
        self,
1056
        qubit_op: BaseOperator,
1057
        offset: float = 0.0,
1058
        linear: bool = False,
1059
    ) -> None:
1060
        r"""Create a quadratic program from a qubit operator and a shift value.
1061

1062
        Variables are mapped to qubits in the same order, i.e.,
1063
        i-th variable is mapped to i-th qubit.
1064
        See https://github.com/Qiskit/qiskit-terra/issues/1148 for details.
1065

1066
        Args:
1067
            qubit_op: The qubit operator of the problem.
1068
            offset: The constant value in the Ising Hamiltonian.
1069
            linear: If linear is True, :math:`x^2` is treated as a linear term
1070
                since :math:`x^2 = x` for :math:`x \in \{0,1\}`.
1071
                Else, :math:`x^2` is treated as a quadratic term.
1072
                The default value is False.
1073

1074
        Raises:
1075
            QiskitOptimizationError: If there are Pauli Xs in any Pauli term
1076
            QiskitOptimizationError: If there are more than 2 Pauli Zs in any Pauli term
1077
            NotImplementedError: If the input operator is a ListOp
1078
        """
1079
        # pylint: disable=cyclic-import
1080
        from ..translators.ising import from_ising
1✔
1081

1082
        other = from_ising(qubit_op, offset, linear)
1✔
1083
        self._copy_from(other, include_name=False)
1✔
1084

1085
    def get_feasibility_info(
1✔
1086
        self, x: Union[List[float], np.ndarray]
1087
    ) -> Tuple[bool, List[Variable], List[Constraint]]:
1088
        """Returns whether a solution is feasible or not along with the violations.
1089
        Args:
1090
            x: a solution value, such as returned in an optimizer result.
1091
        Returns:
1092
            feasible: Whether the solution provided is feasible or not.
1093
            List[Variable]: List of variables which are violated.
1094
            List[Constraint]: List of constraints which are violated.
1095

1096
        Raises:
1097
            QiskitOptimizationError: If the input `x` is not same len as total vars
1098
        """
1099
        # if input `x` is not the same len as the total vars, raise an error
1100
        if len(x) != self.get_num_vars():
1✔
1101
            raise QiskitOptimizationError(
×
1102
                f"The size of solution `x`: {len(x)}, does not match the number of problem variables: "
1103
                f"{self.get_num_vars()}"
1104
            )
1105

1106
        # check whether the input satisfy the bounds of the problem
1107
        violated_variables = []
1✔
1108
        for i, val in enumerate(x):
1✔
1109
            variable = self.get_variable(i)
1✔
1110
            if val < variable.lowerbound or variable.upperbound < val:
1✔
1111
                violated_variables.append(variable)
1✔
1112

1113
        # check whether the input satisfy the constraints of the problem
1114
        violated_constraints = []
1✔
1115
        for constraint in cast(List[Constraint], self._linear_constraints) + cast(
1✔
1116
            List[Constraint], self._quadratic_constraints
1117
        ):
1118
            lhs = constraint.evaluate(x)
1✔
1119
            if constraint.sense == ConstraintSense.LE and lhs > constraint.rhs:
1✔
1120
                violated_constraints.append(constraint)
1✔
1121
            elif constraint.sense == ConstraintSense.GE and lhs < constraint.rhs:
1✔
1122
                violated_constraints.append(constraint)
1✔
1123
            elif constraint.sense == ConstraintSense.EQ and not isclose(lhs, constraint.rhs):
1✔
1124
                violated_constraints.append(constraint)
1✔
1125

1126
        feasible = not violated_variables and not violated_constraints
1✔
1127

1128
        return feasible, violated_variables, violated_constraints
1✔
1129

1130
    def is_feasible(self, x: Union[List[float], np.ndarray]) -> bool:
1✔
1131
        """Returns whether a solution is feasible or not.
1132

1133
        Args:
1134
            x: a solution value, such as returned in an optimizer result.
1135

1136
        Returns:
1137
            ``True`` if the solution provided is feasible otherwise ``False``.
1138

1139
        """
1140
        feasible, _, _ = self.get_feasibility_info(x)
1✔
1141

1142
        return feasible
1✔
1143

1144
    def prettyprint(self, wrap: int = 80) -> str:
1✔
1145
        """Returns a pretty printed string of this problem.
1146

1147
        Args:
1148
            wrap: The text width to wrap the output strings. It is disabled by setting 0.
1149
                Note that some strings might exceed this value, for example, a long variable
1150
                name won't be wrapped. The default value is 80.
1151

1152
        Returns:
1153
            A pretty printed string representing the problem.
1154

1155
        Raises:
1156
            QiskitOptimizationError: if there is a non-printable name.
1157
        """
1158
        # pylint: disable=cyclic-import
1159
        from qiskit_optimization.translators.prettyprint import prettyprint
1✔
1160

1161
        return prettyprint(self, wrap)
1✔
1162

1163
    @staticmethod
1✔
1164
    def _check_name(name: str, name_type: str) -> None:
1✔
1165
        """Displays a warning message if a name string is not printable"""
1166
        if not name.isprintable():
1✔
1167
            warn(f"{name_type} name is not printable: {repr(name)}")
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc