Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

INTERNAL ERROR: Black produced different code on the second pass of the formatter. #1903

Closed
Viech opened this issue Jan 4, 2021 · 2 comments
Labels
C: unstable formatting Formatting changed on the second pass R: duplicate This issue or pull request already exists T: bug Something isn't working

Comments

@Viech
Copy link

Viech commented Jan 4, 2021

File is found here, formatting with black -l 80, version is 20.8b1.

Mode(target_versions=set(), line_length=80, string_normalization=True, experimental_string_processing=False, is_pyi=False)
--- source
+++ first pass
@@ -76,14 +76,14 @@
 
     #: Short string denoting a feasibility problem.
     FIND = "find"
 
     #: Short string denoting a minimization problem.
-    MIN  = "min"
+    MIN = "min"
 
     #: Short string denoting a maximization problem.
-    MAX  = "max"
+    MAX = "max"
 
     def __init__(self, direction=None, function=None):
         """Construct an optimization objective.
 
         :param str direction:
@@ -110,31 +110,37 @@
                 direction = self.MIN
             elif lower.startswith("max"):
                 direction = self.MAX
             else:
                 raise ValueError(
-                    "Invalid search direction '{}'.".format(direction))
+                    "Invalid search direction '{}'.".format(direction)
+                )
 
         if function is None:
             if direction != self.FIND:
                 raise ValueError("Missing an objective function.")
         else:
             if direction == self.FIND:
-                raise ValueError("May not specify an objective function for a "
-                    "feasiblity problem.")
+                raise ValueError(
+                    "May not specify an objective function for a "
+                    "feasiblity problem."
+                )
 
             if not isinstance(function, expressions.Expression):
                 raise TypeError(
-                    "Objective function must be a PICOS expression.")
+                    "Objective function must be a PICOS expression."
+                )
 
             if len(function) != 1:
                 raise TypeError("Objective function must be scalar.")
 
             function = function.refined
 
-            if isinstance(function, expressions.ComplexAffineExpression) \
-            and function.complex:
+            if (
+                isinstance(function, expressions.ComplexAffineExpression)
+                and function.complex
+            ):
                 raise TypeError("Objective function may not be complex.")
 
         self._direction = direction
         self._function = function
 
@@ -145,11 +151,12 @@
             minimize = self._direction == self.MIN
             dir_str = "minimize" if minimize else "maximize"
 
             if self._function.uncertain:
                 obj_str = self._function.worst_case_string(
-                    "max" if minimize else "min")
+                    "max" if minimize else "min"
+                )
             else:
                 obj_str = self._function.string
 
             return "{} {}".format(dir_str, obj_str)
 
@@ -233,56 +240,71 @@
                 bad_direction = self.FIND
 
             try:
                 return self._function.worst_case_value(bad_direction)
             except IntractableWorstCase as error:
-                raise IntractableWorstCase("Failed to compute the worst-case "
+                raise IntractableWorstCase(
+                    "Failed to compute the worst-case "
                     "value of the objective function {}: {} Maybe evaluate the "
-                    "nominal objective function instead?"
-                    .format(self._function.string, error))
+                    "nominal objective function instead?".format(
+                        self._function.string, error
+                    )
+                )
         else:
             return self._function.value
 
     def __index__(self):
         if self._function is None:
-            raise TypeError("A feasiblity objective cannot be used as an index "
-                "because there is no objective function to take the value of.")
+            raise TypeError(
+                "A feasiblity objective cannot be used as an index "
+                "because there is no objective function to take the value of."
+            )
 
         value = self.value
 
         if value is None:
             raise expressions.NotValued(
-                "Cannot use unvalued objective function {} as an index."
-                .format(self._function.string))
+                "Cannot use unvalued objective function {} as an index.".format(
+                    self._function.string
+                )
+            )
 
         try:
             fltValue = float(value)
             intValue = int(fltValue)
 
             if intValue != fltValue:
                 raise ValueError
         except (TypeError, ValueError):
-            raise RuntimeError("Cannot use the objective function {} as an "
-                "index as its value of {} is not integral."
-                .format(self._function.string, value))
+            raise RuntimeError(
+                "Cannot use the objective function {} as an "
+                "index as its value of {} is not integral.".format(
+                    self._function.string, value
+                )
+            )
 
         return intValue
 
     def _casting_helper(self, theType):
         assert theType in (int, float, complex)
 
         if self._function is None:
-            raise TypeError("A feasiblity objective cannot be cast as {} "
-                "because there is no objective function to take the value of."
-                .format(theType.__name__))
+            raise TypeError(
+                "A feasiblity objective cannot be cast as {} "
+                "because there is no objective function to take the value of.".format(
+                    theType.__name__
+                )
+            )
 
         value = self.value
 
         if value is None:
             raise expressions.NotValued(
-                "Cannot cast unvalued objective function {} as {}."
-                .format(self._function.string, theType.__name__))
+                "Cannot cast unvalued objective function {} as {}.".format(
+                    self._function.string, theType.__name__
+                )
+            )
 
         return theType(value)
 
     def __int__(self):
         return self._casting_helper(int)
@@ -295,11 +317,11 @@
 
     def __round__(self, ndigits=None):
         return round(float(self), ndigits)
 
 
-class Problem():
+class Problem:
     """PICOS' representation of an optimization problem.
 
     :Example:
 
     >>> from picos import Problem, RealVariable
@@ -332,14 +354,19 @@
     """
 
     #: The specification for problems returned by :meth:`conic_form`.
     CONIC_FORM = Specification(
         objectives=[expressions.AffineExpression],
-        constraints=[C for C in
-            (getattr(constraints, Cname) for Cname in constraints.__all__)
+        constraints=[
+            C
+            for C in (
+                getattr(constraints, Cname) for Cname in constraints.__all__
+            )
             if issubclass(C, constraints.ConicConstraint)
-            and C is not constraints.ConicConstraint])
+            and C is not constraints.ConicConstraint
+        ],
+    )
 
     # --------------------------------------------------------------------------
     # Initialization and reset methods.
     # --------------------------------------------------------------------------
 
@@ -357,11 +384,12 @@
             A sequence of additional solver options to apply on top of the
             default options or those given by ``copyOptions`` or ``useOptions``.
         """
         if copyOptions and useOptions:
             raise ValueError(
-                "Can only copy or use existing solver options, not both.")
+                "Can only copy or use existing solver options, not both."
+            )
 
         extra_options = map_legacy_options(**extra_options)
 
         if copyOptions:
             self._options = copyOptions.copy()
@@ -550,12 +578,14 @@
         return self._options
 
     @options.setter
     def options(self, value):
         if not isinstance(value, Options):
-            raise TypeError("Cannot assign an object of type {} as a problem's "
-                " options.".format(type(value).__name__))
+            raise TypeError(
+                "Cannot assign an object of type {} as a problem's "
+                " options.".format(type(value).__name__)
+            )
 
         self._options = value
 
     @options.deleter
     def options(self, value):
@@ -615,16 +645,20 @@
     def strategy(self, value):
         from .strategy import Strategy
 
         if not isinstance(value, Strategy):
             raise TypeError(
-                "Cannot assign an object of type {} as a solution strategy."
-                .format(type(value).__name__))
+                "Cannot assign an object of type {} as a solution strategy.".format(
+                    type(value).__name__
+                )
+            )
 
         if value.problem is not self:
-            raise ValueError("The solution strategy was constructed for a "
-                "different problem.")
+            raise ValueError(
+                "The solution strategy was constructed for a "
+                "different problem."
+            )
 
         self._strategy = value
 
     @strategy.deleter
     def strategy(self):
@@ -651,18 +685,20 @@
     @property
     def continuous(self):
         """Whether all variables are of continuous types."""
         return all(
             isinstance(variable, expressions.CONTINUOUS_VARTYPES)
-            for variable in self._variables.values())
+            for variable in self._variables.values()
+        )
 
     @property
     def pure_integer(self):
         """Whether all variables are of integral types."""
         return not any(
             isinstance(variable, expressions.CONTINUOUS_VARTYPES)
-            for variable in self._variables.values())
+            for variable in self._variables.values()
+        )
 
     @property
     def type(self):
         """The problem type as a string, such as "Linear Program"."""
         C = set(type(c) for c in self._constraints.values())
@@ -672,30 +708,30 @@
         linear = [
             constraints.AffineConstraint,
             constraints.ComplexAffineConstraint,
             constraints.AbsoluteValueConstraint,
             constraints.SimplexConstraint,
-            constraints.FlowConstraint]
-        sdp = [
-            constraints.LMIConstraint,
-            constraints.ComplexLMIConstraint]
+            constraints.FlowConstraint,
+        ]
+        sdp = [constraints.LMIConstraint, constraints.ComplexLMIConstraint]
         quadratic = [
             constraints.ConvexQuadraticConstraint,
             constraints.ConicQuadraticConstraint,
-            constraints.NonconvexQuadraticConstraint]
-        quadconic = [
-            constraints.SOCConstraint,
-            constraints.RSOCConstraint]
+            constraints.NonconvexQuadraticConstraint,
+        ]
+        quadconic = [constraints.SOCConstraint, constraints.RSOCConstraint]
         exponential = [
             constraints.ExpConeConstraint,
             constraints.SumExponentialsConstraint,
             constraints.LogSumExpConstraint,
             constraints.LogConstraint,
-            constraints.KullbackLeiblerConstraint]
+            constraints.KullbackLeiblerConstraint,
+        ]
         complex = [
             constraints.ComplexAffineConstraint,
-            constraints.ComplexLMIConstraint]
+            constraints.ComplexLMIConstraint,
+        ]
 
         if objective is None:
             if not C:
                 base = "Empty Problem"
             elif C.issubset(set(linear)):
@@ -767,10 +803,11 @@
             This property is intended for educational purposes.
             If you want to solve the primal problem via its dual, use the
             :ref:`dualize <option_dualize>` option instead.
         """
         from ..reforms import Dualization
+
         return self.reformulated(Dualization.SUPPORTED, dualize=True)
 
     @property
     def conic_form(self):
         """The problem in conic form.
@@ -859,14 +896,18 @@
         if len(group) == 1:
             return group[0].long_string
 
         try:
             template, data = parameterized_string(
-                [mtb.long_string for mtb in group])
+                [mtb.long_string for mtb in group]
+            )
         except ValueError:
-            return group[0].long_string \
-                + ", " + ", ".join([v.name for v in group[1:]])
+            return (
+                group[0].long_string
+                + ", "
+                + ", ".join([v.name for v in group[1:]])
+            )
         else:
             return glyphs.forall(template, data)
 
     @lru_cache()
     def _con_group_string(self, group):
@@ -893,19 +934,21 @@
 
         # Print objective.
         string += "  {}\n".format(self._objective)
 
         wrapper = TextWrapper(
-            initial_indent=" "*4,
-            subsequent_indent=" "*6,
+            initial_indent=" " * 4,
+            subsequent_indent=" " * 6,
             break_long_words=False,
-            break_on_hyphens=False)
+            break_on_hyphens=False,
+        )
 
         # Print variables.
         if self._variables:
             string += "  {}\n".format(
-                "for" if self._objective.direction == "find" else "over")
+                "for" if self._objective.direction == "find" else "over"
+            )
             for group in self._var_groups:
                 string += wrapper.fill(self._mtb_group_string(tuple(group)))
                 string += "\n"
 
         # Print constraints.
@@ -945,34 +988,46 @@
 
     def _register_mutables(self, mtbs):
         """Register the mutables of an objective function or constraint."""
         # Register every mutable at most once per call.
         if not isinstance(mtbs, (set, frozenset)):
-            raise TypeError("Mutable registry can (un)register a mutable "
-                "only once per call, so the argument must be a set type.")
+            raise TypeError(
+                "Mutable registry can (un)register a mutable "
+                "only once per call, so the argument must be a set type."
+            )
 
         # Retrieve old and new mutables as mapping from name to object.
         old_mtbs = self._mutables
         new_mtbs = OrderedDict(
-            (mtb.name, mtb) for mtb in sorted(mtbs, key=(lambda m: m.name)))
-        new_vars = OrderedDict((name, mtb) for name, mtb in new_mtbs.items()
-            if isinstance(mtb, BaseVariable))
-        new_prms = OrderedDict((name, mtb) for name, mtb in new_mtbs.items()
-            if not isinstance(mtb, BaseVariable))
+            (mtb.name, mtb) for mtb in sorted(mtbs, key=(lambda m: m.name))
+        )
+        new_vars = OrderedDict(
+            (name, mtb)
+            for name, mtb in new_mtbs.items()
+            if isinstance(mtb, BaseVariable)
+        )
+        new_prms = OrderedDict(
+            (name, mtb)
+            for name, mtb in new_mtbs.items()
+            if not isinstance(mtb, BaseVariable)
+        )
 
         # Check for mutable name clashes within the new set.
         if len(new_mtbs) != len(mtbs):
             raise ValueError(
                 "The object you are trying to add to a problem contains "
-                "multiple mutables of the same name. This is not allowed.")
+                "multiple mutables of the same name. This is not allowed."
+            )
 
         # Check for mutable name clashes with existing mutables.
         for name in set(old_mtbs).intersection(set(new_mtbs)):
             if old_mtbs[name] is not new_mtbs[name]:
-                raise ValueError("Cannot register the mutable {} with the "
+                raise ValueError(
+                    "Cannot register the mutable {} with the "
                     "problem because it already tracks another mutable with "
-                    "the same name.".format(name))
+                    "the same name.".format(name)
+                )
 
         # Keep track of new mutables.
         self._mutables.update(new_mtbs)
         self._variables.update(new_vars)
         self._parameters.update(new_prms)
@@ -984,21 +1039,25 @@
 
     def _unregister_mutables(self, mtbs):
         """Unregister the mutables of an objective function or constraint."""
         # Unregister every mutable at most once per call.
         if not isinstance(mtbs, (set, frozenset)):
-            raise TypeError("Mutable registry can (un)register a mutable "
-                "only once per call, so the argument must be a set type.")
+            raise TypeError(
+                "Mutable registry can (un)register a mutable "
+                "only once per call, so the argument must be a set type."
+            )
 
         for mtb in mtbs:
             name = mtb.name
 
             # Make sure the mutable is properly registered.
-            assert name in self._mutables and mtb in self._mtb_count, \
-                "Tried to unregister a mutable that is not registered."
-            assert self._mtb_count[mtb] >= 1, \
-                "Found a nonpostive mutable count."
+            assert (
+                name in self._mutables and mtb in self._mtb_count
+            ), "Tried to unregister a mutable that is not registered."
+            assert (
+                self._mtb_count[mtb] >= 1
+            ), "Found a nonpostive mutable count."
 
             # Count down the mutable references.
             self._mtb_count[mtb] -= 1
 
             # Remove a mutable with a reference count of zero.
@@ -1049,20 +1108,25 @@
             elif idOrIndOrCon < len(self._constraints):
                 # An offset.
                 return list(self._constraints.keys())[idOrIndOrCon]
             else:
                 raise LookupError(
-                    "The problem has no constraint with ID or offset {}."
-                    .format(idOrIndOrCon))
+                    "The problem has no constraint with ID or offset {}.".format(
+                        idOrIndOrCon
+                    )
+                )
         elif isinstance(idOrIndOrCon, constraints.Constraint):
             # A constraint object.
             id = idOrIndOrCon.id
             if id in self._constraints:
                 return id
             else:
-                raise KeyError("The constraint '{}' is not part of the problem."
-                    .format(idOrIndOrCon))
+                raise KeyError(
+                    "The constraint '{}' is not part of the problem.".format(
+                        idOrIndOrCon
+                    )
+                )
         elif isinstance(idOrIndOrCon, tuple) or isinstance(idOrIndOrCon, list):
             if len(idOrIndOrCon) == 1:
                 groupIndex = idOrIndOrCon[0]
                 if groupIndex < len(self._con_groups):
                     return [c.id for c in self._con_groups[groupIndex]]
@@ -1074,19 +1138,24 @@
                     group = self._con_groups[groupIndex]
                     if groupOffset < len(group):
                         return group[groupOffset].id
                     else:
                         raise IndexError(
-                            "Constraint group offset out of range.")
+                            "Constraint group offset out of range."
+                        )
                 else:
                     raise IndexError("Constraint group index out of range.")
             else:
-                raise TypeError("If looking up constraints by group, the index "
-                    "must be a tuple or list of length at most two.")
+                raise TypeError(
+                    "If looking up constraints by group, the index "
+                    "must be a tuple or list of length at most two."
+                )
         else:
-            raise TypeError("Argument of type '{}' not supported when looking "
-                "up constraints".format(type(idOrIndOrCon)))
+            raise TypeError(
+                "Argument of type '{}' not supported when looking "
+                "up constraints".format(type(idOrIndOrCon))
+            )
 
     def get_constraint(self, idOrIndOrCon):
         """Return a (list of) constraint(s) of the problem.
 
         :param idOrIndOrCon: One of the following:
@@ -1162,11 +1231,12 @@
         :returns: The constraint that was added to the problem.
         """
         # Handle deprecated 'key' parameter.
         if key is not None:
             throw_deprecation_warning(
-                "Naming constraints is currently not supported.")
+                "Naming constraints is currently not supported."
+            )
 
         # Register the constraint.
         self._constraints[constraint.id] = constraint
         self._con_groups.append([constraint])
 
@@ -1223,12 +1293,14 @@
             ‖w[i,j]‖ ≤ y[j] ∀ (i,j) ∈ zip([1,2,4],[2,0,2])
             y[i] ≥ y[i+1] ∀ i ∈ [0…3]
         """
         if it is not None or indices is not None or key is not None:
             # Deprecated as of 2.0.
-            throw_deprecation_warning("Arguments 'it', 'indices' and 'key' to "
-                "add_list_of_constraints are deprecated and ignored.")
+            throw_deprecation_warning(
+                "Arguments 'it', 'indices' and 'key' to "
+                "add_list_of_constraints are deprecated and ignored."
+            )
 
         added = []
         for constraint in lst:
             added.append(self.add_constraint(constraint))
             self._con_groups.pop()
@@ -1252,12 +1324,14 @@
             for j, candidate in enumerate(group):
                 if candidate is constraint:
                     return i, j
 
         if constraint in self._constraints.values():
-            raise RuntimeError("The problem's constraint and constraint group "
-                "registries are out of sync.")
+            raise RuntimeError(
+                "The problem's constraint and constraint group "
+                "registries are out of sync."
+            )
         else:
             raise KeyError("The constraint is not part of the problem.")
 
     def remove_constraint(self, idOrIndOrCon):
         """Delete a constraint from the problem.
@@ -1439,19 +1513,25 @@
                     intParams = sorted([int(p) for p in params])
                 except ValueError:
                     pass
                 else:
                     if intParams == list(range(len(intParams))):
-                        return [self._variables["{}[{}]".format(name, param)]
-                            for param in intParams]
+                        return [
+                            self._variables["{}[{}]".format(name, param)]
+                            for param in intParams
+                        ]
 
                 # Otherwise return a dict.
-                return {param: self._variables["{}[{}]".format(name, param)]
-                    for param in params}
+                return {
+                    param: self._variables["{}[{}]".format(name, param)]
+                    for param in params
+                }
             else:
-                raise KeyError("The problem references no variable or group of "
-                    "variables named '{}'.".format(name))
+                raise KeyError(
+                    "The problem references no variable or group of "
+                    "variables named '{}'.".format(name)
+                )
 
     def get_valued_variable(self, name):
         """Retrieve values of variables referenced by the problem.
 
         This method works the same :meth:`get_variable` but it returns the
@@ -1484,11 +1564,12 @@
         new_mtbs = {mtb: mtb.copy() for name, mtb in self._mutables.items()}
 
         # Make copies of constraints on top of the new mutables.
         for group in self._con_groups:
             the_copy.add_list_of_constraints(
-                constraint.replace_mutables(new_mtbs) for constraint in group)
+                constraint.replace_mutables(new_mtbs) for constraint in group
+            )
 
         # Make a copy of the objective on top of the new mutables.
         direction, function = self._objective
         if function is not None:
             the_copy.objective = direction, function.replace_mutables(new_mtbs)
@@ -1510,11 +1591,12 @@
         # Relax integral variables and copy other mutables if requested.
         new_mtbs = {}
         for name, var in self._mutables.items():
             if isinstance(var, expressions.IntegerVariable):
                 new_mtbs[name] = expressions.RealVariable(
-                    name, var.shape, var._lower, var._upper)
+                    name, var.shape, var._lower, var._upper
+                )
             elif isinstance(var, expressions.BinaryVariable):
                 new_mtbs[name] = expressions.RealVariable(name, var.shape, 0, 1)
             else:
                 if copy_other_mutables:
                     new_mtbs[name] = var.copy()
@@ -1522,11 +1604,12 @@
                     new_mtbs[name] = var
 
         # Make copies of constraints on top of the new mutables.
         for group in self._con_groups:
             the_copy.add_list_of_constraints(
-                constraint.replace_mutables(new_mtbs) for constraint in group)
+                constraint.replace_mutables(new_mtbs) for constraint in group
+            )
 
         # Make a copy of the objective on top of the new mutables.
         direction, function = self._objective
         if function is not None:
             the_copy.objective = direction, function.replace_mutables(new_mtbs)
@@ -1658,19 +1741,27 @@
             steps = numReforms
 
         if steps == 0:
             return self
         elif steps > numReforms:
-            raise ValueError("The pipeline {} has only {} reformulation steps "
-                "to choose from.".format(strategy, numReforms))
+            raise ValueError(
+                "The pipeline {} has only {} reformulation steps "
+                "to choose from.".format(strategy, numReforms)
+            )
 
         # Replace the successor of the last reformulation with a dummy solver.
         lastReform = strategy.reforms[steps - 1]
         oldSuccessor = lastReform.successor
-        lastReform.successor = type("DummySolver", (), {
-            "execute": lambda self: Solution(
-                {}, solver="dummy", vectorizedPrimals=True)})()
+        lastReform.successor = type(
+            "DummySolver",
+            (),
+            {
+                "execute": lambda self: Solution(
+                    {}, solver="dummy", vectorizedPrimals=True
+                )
+            },
+        )()
 
         # Execute the cut-short strategy.
         strategy.execute(**extra_options)
 
         # Repair the last reformulation.
@@ -1742,38 +1833,51 @@
             This method is intended for educational purposes.
             You do not need to use it when solving a problem as PICOS will
             perform the necessary reformulations automatically.
         """
         if not isinstance(specification, Specification):
-            raise TypeError("The desired problem type must be given as a "
-                "Specification object.")
+            raise TypeError(
+                "The desired problem type must be given as a "
+                "Specification object."
+            )
 
         # Create a placeholder function for abstract methods of a dummy solver.
         def placeholder(the_self):
-            raise RuntimeError("The dummy solver created by "
-                "Problem.reformulated must not be executed.")
+            raise RuntimeError(
+                "The dummy solver created by "
+                "Problem.reformulated must not be executed."
+            )
 
         # Declare a dummy solver that accepts specified problems.
-        DummySolver = type("DummySolver", (Solver,), {
-            # Abstract class methods.
-            "supports": classmethod(lambda cls, footprint:
-                Solver.supports(footprint) and footprint in specification),
-            "default_penalty": classmethod(lambda cls: 0),
-            "test_availability": classmethod(lambda cls: None),
-            "names": classmethod(lambda cls: ("Dummy Solver", "DummySolver",
-                "Dummy Solver accepting {}".format(specification))),
-            "is_free": classmethod(lambda cls: True),
-
-            # Additional class methods needed for an ad-hoc solver.
-            "penalty": classmethod(lambda cls, options: 0),
-
-            # Abstract instance methods.
-            "reset_problem": lambda self: placeholder(self),
-            "_import_problem": lambda self: placeholder(self),
-            "_update_problem": lambda self: placeholder(self),
-            "_solve": lambda self: placeholder(self)
-        })
+        DummySolver = type(
+            "DummySolver",
+            (Solver,),
+            {
+                # Abstract class methods.
+                "supports": classmethod(
+                    lambda cls, footprint: Solver.supports(footprint)
+                    and footprint in specification
+                ),
+                "default_penalty": classmethod(lambda cls: 0),
+                "test_availability": classmethod(lambda cls: None),
+                "names": classmethod(
+                    lambda cls: (
+                        "Dummy Solver",
+                        "DummySolver",
+                        "Dummy Solver accepting {}".format(specification),
+                    )
+                ),
+                "is_free": classmethod(lambda cls: True),
+                # Additional class methods needed for an ad-hoc solver.
+                "penalty": classmethod(lambda cls, options: 0),
+                # Abstract instance methods.
+                "reset_problem": lambda self: placeholder(self),
+                "_import_problem": lambda self: placeholder(self),
+                "_update_problem": lambda self: placeholder(self),
+                "_solve": lambda self: placeholder(self),
+            },
+        )
 
         # Ad-hoc the dummy solver and prepare the problem for it.
         oldAdHocSolver = self.options.ad_hoc_solver
         extra_options["ad_hoc_solver"] = DummySolver
         problem = self.prepared(**extra_options)
@@ -1861,71 +1965,97 @@
                     elif options.solver:
                         solverName = get_solver(options.solver).names()[1]
                     else:
                         solverName = None
 
-                    print("Searching a solution strategy{}.".format(
-                    " for {}".format(solverName) if solverName else ""))
+                    print(
+                        "Searching a solution strategy{}.".format(
+                            " for {}".format(solverName) if solverName else ""
+                        )
+                    )
 
                 try:
                     self._strategy = Strategy.from_problem(
-                        self, **extra_options)
+                        self, **extra_options
+                    )
                 except NoStrategyFound as error:
                     raise SolutionFailure(1, error)
 
                 if verbose:
-                    print("Solution strategy:\n  {}".format(
-                        "\n  ".join(str(self._strategy).splitlines())))
+                    print(
+                        "Solution strategy:\n  {}".format(
+                            "\n  ".join(str(self._strategy).splitlines())
+                        )
+                    )
             else:
                 if verbose:
-                    print("Reusing strategy:\n  {}".format(
-                        "\n  ".join(str(self._strategy).splitlines())))
+                    print(
+                        "Reusing strategy:\n  {}".format(
+                            "\n  ".join(str(self._strategy).splitlines())
+                        )
+                    )
 
             # Execute the strategy to obtain one or more solutions.
             solutions = self._strategy.execute(**extra_options)
 
             # Report how many solutions were obtained, select the first.
             if isinstance(solutions, list):
                 assert all(isinstance(s, Solution) for s in solutions)
 
                 if not solutions:
                     raise SolutionFailure(
-                        2, "The solver returned an empty list of solutions.")
+                        2, "The solver returned an empty list of solutions."
+                    )
 
                 solution = solutions[0]
 
                 if verbose:
-                    print("Selecting the first of {} solutions obtained for "
-                        "processing.".format(len(solutions)))
+                    print(
+                        "Selecting the first of {} solutions obtained for "
+                        "processing.".format(len(solutions))
+                    )
             else:
                 assert isinstance(solutions, Solution)
                 solution = solutions
 
             # Report claimed solution state.
             if verbose:
-                print("Solver claims {} solution for {} problem.".format(
-                    solution.claimedStatus, solution.problemStatus))
+                print(
+                    "Solver claims {} solution for {} problem.".format(
+                        solution.claimedStatus, solution.problemStatus
+                    )
+                )
 
             # Validate the primal solution.
             if options.primals:
                 if solution.primalStatus != SS_OPTIMAL:
-                    raise SolutionFailure(3, "Primal solution state claimed {} "
+                    raise SolutionFailure(
+                        3,
+                        "Primal solution state claimed {} "
                         "but optimality is required (primals=True).".format(
-                        solution.primalStatus))
+                            solution.primalStatus
+                        ),
+                    )
                 elif None in solution.primals:
                     raise SolutionFailure(
-                        "The primal solution is incomplete (primals=True).")
+                        "The primal solution is incomplete (primals=True)."
+                    )
 
             # Validate the dual solution.
             if options.duals:
                 if solution.dualStatus != SS_OPTIMAL:
-                    raise SolutionFailure(4, "Dual solution state claimed {} "
+                    raise SolutionFailure(
+                        4,
+                        "Dual solution state claimed {} "
                         "but optimality is required (duals=True).".format(
-                        solution.dualStatus))
+                            solution.dualStatus
+                        ),
+                    )
                 elif None in solution.duals:
                     raise SolutionFailure(
-                        "The dual solution is incomplete (duals=True).")
+                        "The dual solution is incomplete (duals=True)."
+                    )
 
             if options.apply_solution:
                 if verbose:
                     print("Applying the solution.")
 
@@ -1947,17 +2077,23 @@
                 overhead = (solveTime - searchTime) / searchTime
             else:
                 overhead = float("inf")
 
             if verbose:
-                print("Search {:.1e}s, solve {:.1e}s, overhead {:.0%}."
-                    .format(searchTime, solveTime, overhead))
+                print(
+                    "Search {:.1e}s, solve {:.1e}s, overhead {:.0%}.".format(
+                        searchTime, solveTime, overhead
+                    )
+                )
 
         return solutions
 
-    @deprecated("2.0", reason="Misleading semantics. Maybe "
-        ":func:`picos.minimize` is what you want.")
+    @deprecated(
+        "2.0",
+        reason="Misleading semantics. Maybe "
+        ":func:`picos.minimize` is what you want.",
+    )
     def minimize(self, obj, **extra_options):
         """Look for a minimizing solution.
 
         Sets the objective to minimize the given objective function and calls
         the solver with the given additional options.
@@ -1975,12 +2111,15 @@
             override any existing objective function and direction.
         """
         self.objective = "min", obj
         return self.solve(**extra_options)
 
-    @deprecated("2.0", reason="Misleading semantics. Maybe "
-        ":func:`picos.maximize` is what you want.")
+    @deprecated(
+        "2.0",
+        reason="Misleading semantics. Maybe "
+        ":func:`picos.maximize` is what you want.",
+    )
     def maximize(self, obj, **extra_options):
         """Look for a maximization solution.
 
         Sets the objective to maximize the given objective function and calls
         the solver with the given additional options.
@@ -2034,30 +2173,36 @@
         :raises picos.uncertain.IntractableWorstCase:
             When computing the worst-case (expected) value of the constrained
             expression is not supported.
         """
         if inttol is not None:
-            throw_deprecation_warning("Variable integrality is now ensured on "
+            throw_deprecation_warning(
+                "Variable integrality is now ensured on "
                 "assignment of a value, so it does not need to be checked via "
-                "check_current_value_feasibility's old 'inttol' parameter.")
+                "check_current_value_feasibility's old 'inttol' parameter."
+            )
 
         if tol is None:
             tol = self._options.abs_prim_fsb_tol
 
         all_cons = list(self._constraints.values())
         all_cons += [
-            variable.bound_constraint for variable in self._variables.values()
-            if variable.bound_constraint]
+            variable.bound_constraint
+            for variable in self._variables.values()
+            if variable.bound_constraint
+        ]
 
         largest_violation = 0.0
 
         for constraint in all_cons:
             try:
                 slack = constraint.slack
             except IntractableWorstCase as error:
-                raise IntractableWorstCase("Failed to check worst-case or "
-                    "expected feasibility of {}: {}".format(constraint, error))
+                raise IntractableWorstCase(
+                    "Failed to check worst-case or "
+                    "expected feasibility of {}: {}".format(constraint, error)
+                )
 
             assert isinstance(slack, (float, cvx.matrix, cvx.spmatrix))
             if isinstance(slack, (float, cvx.spmatrix)):
                 slack = cvx.matrix(slack)  # Allow min, max.
 
@@ -2070,13 +2215,15 @@
             #        cone's slack can then have negative entries but still be
             #        feasible and declared infeasible here.
             # TODO: Add a "violation" interface to Constraint that replaces all
             #       the logic below.
             from ..expressions import Constant, PositiveSemidefiniteCone
-            if isinstance(constraint,
-                constraints.uncertain.ScenarioUncertainConicConstraint) \
-            and isinstance(constraint.cone, PositiveSemidefiniteCone):
+
+            if isinstance(
+                constraint,
+                constraints.uncertain.ScenarioUncertainConicConstraint,
+            ) and isinstance(constraint.cone, PositiveSemidefiniteCone):
                 hack = True
                 slack = Constant(slack).desvec.safe_value
             else:
                 hack = False
 
@@ -2099,12 +2246,14 @@
 
     # --------------------------------------------------------------------------
     # Legacy methods and properties.
     # --------------------------------------------------------------------------
 
-    _LEGACY_PROPERTY_REASON = "Still used internally by legacy code; will be " \
+    _LEGACY_PROPERTY_REASON = (
+        "Still used internally by legacy code; will be "
         "removed together with that code."
+    )
 
     @property
     @deprecated("2.0", reason=_LEGACY_PROPERTY_REASON)
     def countVar(self):
         """The same as :func:`len` applied to :attr:`variables`."""
@@ -2124,35 +2273,62 @@
 
     @property
     @deprecated("2.0", reason=_LEGACY_PROPERTY_REASON)
     def numberLSEConstraints(self):
         """Number of :class:`~picos.constraints.LogSumExpConstraint` stored."""
-        return len([c for c in self._constraints.values()
-            if isinstance(c, constraints.LogSumExpConstraint)])
+        return len(
+            [
+                c
+                for c in self._constraints.values()
+                if isinstance(c, constraints.LogSumExpConstraint)
+            ]
+        )
 
     @property
     @deprecated("2.0", reason=_LEGACY_PROPERTY_REASON)
     def numberSDPConstraints(self):
         """Number of :class:`~picos.constraints.LMIConstraint` stored."""
-        return len([c for c in self._constraints.values()
-            if isinstance(c, constraints.LMIConstraint)])
+        return len(
+            [
+                c
+                for c in self._constraints.values()
+                if isinstance(c, constraints.LMIConstraint)
+            ]
+        )
 
     @property
     @deprecated("2.0", reason=_LEGACY_PROPERTY_REASON)
     def numberQuadConstraints(self):
         """Number of quadratic constraints stored."""
-        return len([c for c in self._constraints.values() if isinstance(c, (
-            constraints.ConvexQuadraticConstraint,
-            constraints.ConicQuadraticConstraint,
-            constraints.NonconvexQuadraticConstraint))])
+        return len(
+            [
+                c
+                for c in self._constraints.values()
+                if isinstance(
+                    c,
+                    (
+                        constraints.ConvexQuadraticConstraint,
+                        constraints.ConicQuadraticConstraint,
+                        constraints.NonconvexQuadraticConstraint,
+                    ),
+                )
+            ]
+        )
 
     @property
     @deprecated("2.0", reason=_LEGACY_PROPERTY_REASON)
     def numberConeConstraints(self):
         """Number of quadratic conic constraints stored."""
-        return len([c for c in self._constraints.values() if isinstance(
-            c, (constraints.SOCConstraint, constraints.RSOCConstraint))])
+        return len(
+            [
+                c
+                for c in self._constraints.values()
+                if isinstance(
+                    c, (constraints.SOCConstraint, constraints.RSOCConstraint)
+                )
+            ]
+        )
 
     @deprecated("2.0", useInstead="value")
     def obj_value(self):
         """Objective function value.
 
@@ -2163,17 +2339,21 @@
             to denote an unvalued expression would raise
             :exc:`~picos.expressions.NotValued` instead.
         """
         if self._objective.feasibility:
             raise AttributeError(
-                "A feasibility problem has no objective value.")
+                "A feasibility problem has no objective value."
+            )
 
         value = self.value
 
         if self.value is None:
-            raise AttributeError("The objective {} is not fully valued."
-                .format(self._objective.function.string))
+            raise AttributeError(
+                "The objective {} is not fully valued.".format(
+                    self._objective.function.string
+                )
+            )
         else:
             return value
 
     @deprecated("2.0", useInstead="continuous")
     def is_continuous(self):
@@ -2213,14 +2393,18 @@
     @deprecated("2.0", useInstead="Problem.options")
     def verbosity(self):
         """Return the problem's current verbosity level."""
         return self._options.verbosity
 
-    @deprecated("2.0", reason="Variables can now be created independent of "
-        "problems, and do not need to be added to any problem explicitly.")
+    @deprecated(
+        "2.0",
+        reason="Variables can now be created independent of "
+        "problems, and do not need to be added to any problem explicitly.",
+    )
     def add_variable(
-            self, name, size=1, vtype='continuous', lower=None, upper=None):
+        self, name, size=1, vtype="continuous", lower=None, upper=None
+    ):
         r"""Legacy method to create a PICOS variable.
 
         :param str name: The name of the variable.
 
         :param size:
@@ -2280,18 +2464,23 @@
         elif vtype == "complex":
             return expressions.ComplexVariable(name, size)
         elif vtype == "hermitian":
             return expressions.HermitianVariable(name, size)
         elif vtype in ("semiint", "semicont"):
-            raise NotImplementedError("Variables with legacy types 'semiint' "
+            raise NotImplementedError(
+                "Variables with legacy types 'semiint' "
                 "and 'semicont' are not supported anymore as of PICOS 2.0. "
-                "If you need this functionality back, please open an issue.")
+                "If you need this functionality back, please open an issue."
+            )
         else:
             raise ValueError("Unknown legacy variable type '{}'.".format(vtype))
 
-    @deprecated("2.0", reason="Whether a problem references a variable is now"
-        " determined dynamically, so this method has no effect.")
+    @deprecated(
+        "2.0",
+        reason="Whether a problem references a variable is now"
+        " determined dynamically, so this method has no effect.",
+    )
     def remove_variable(self, name):
         """Does nothing."""
         pass
 
     @deprecated("2.0", useInstead="variables")
@@ -2311,11 +2500,12 @@
         """
         try:
             variable = self._variables[name]
         except KeyError:
             raise KeyError(
-                "The problem references no variable named '{}'.".format(name))
+                "The problem references no variable named '{}'.".format(name)
+            )
         else:
             variable.value = value
 
     @deprecated("2.0", useInstead="dual")
     def as_dual(self):
--- first pass
+++ second pass
@@ -2216,14 +2216,17 @@
             #        feasible and declared infeasible here.
             # TODO: Add a "violation" interface to Constraint that replaces all
             #       the logic below.
             from ..expressions import Constant, PositiveSemidefiniteCone
 
-            if isinstance(
-                constraint,
-                constraints.uncertain.ScenarioUncertainConicConstraint,
-            ) and isinstance(constraint.cone, PositiveSemidefiniteCone):
+            if (
+                isinstance(
+                    constraint,
+                    constraints.uncertain.ScenarioUncertainConicConstraint,
+                )
+                and isinstance(constraint.cone, PositiveSemidefiniteCone)
+            ):
                 hack = True
                 slack = Constant(slack).desvec.safe_value
             else:
                 hack = False
@Viech Viech added the T: bug Something isn't working label Jan 4, 2021
@ichard26 ichard26 added the C: unstable formatting Formatting changed on the second pass label Jan 4, 2021
@mvolfik
Copy link

mvolfik commented Feb 22, 2021

MCVE:

def f():
    def f():
        def f():
            from ..expressions import Constant, PositiveSemidefiniteCone
            if isinstance(constraint,
                constraints.uncertain.ScenarioUncertainConicConstraint) \
            and isinstance(constraint.cone, PositiveSemidefiniteCone):
                pass

Likely a duplicate of #1629 , as this file works after applying #1958

@ichard26 ichard26 added the R: duplicate This issue or pull request already exists label Apr 25, 2021
@ichard26
Copy link
Collaborator

Hello!

All reproduction cases in this issue format without error on master. The fixing commit was 8672af3 from PR GH-2126. I'll be marking this issue as a duplicate of GH-1629 since that's what GH-2126 aimed to fix and it's highly likely this issue falls under GH-1629.

Since we use the issue tracker as a reflection of what's on master, I'll be closing this issue. If you have any issues, especially with the new (but stable) output, please open a new issue. Oh and the fix should be available in a published release soon, see GH-2125 for more info.

Thank you for reporting!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C: unstable formatting Formatting changed on the second pass R: duplicate This issue or pull request already exists T: bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants