parameters: (problem, candidates,
                              gp: bool = False,
                              enforce_dpp: bool = False)
docstring: Build a reduction chain from a problem to an installed solver.
  
      Note that if the supplied problem has 0 variables, then the solver
      parameter will be ignored.
  
      Parameters
      ----------
      problem : Problem
          The problem for which to build a chain.
      candidates : dict
          Dictionary of candidate solvers divided in qp_solvers
          and conic_solvers.
      gp : bool
          If True, the problem is parsed as a Disciplined Geometric Program
          instead of as a Disciplined Convex Program.
      enforce_dpp : bool, optional
          When True, a DPPError will be thrown when trying to parse a non-DPP
          problem (instead of just a warning). Defaults to False.
  
      Returns
      -------
      SolvingChain
          A SolvingChain that can be used to solve the problem.
  
      Raises
      ------
      SolverError
          Raised if no suitable solver exists among the installed solvers, or
          if the target solver is not installed.
docstring_summary: Build a reduction chain from a problem to an installed solver.
docstring_tokens: Build a reduction chain from a problem to an installed solver .
function: def construct_solving_chain(problem, candidates,
                              gp: bool = False,
                              enforce_dpp: bool = False) -> "SolvingChain":
      """Build a reduction chain from a problem to an installed solver.
  
      Note that if the supplied problem has 0 variables, then the solver
      parameter will be ignored.
  
      Parameters
      ----------
      problem : Problem
          The problem for which to build a chain.
      candidates : dict
          Dictionary of candidate solvers divided in qp_solvers
          and conic_solvers.
      gp : bool
          If True, the problem is parsed as a Disciplined Geometric Program
          instead of as a Disciplined Convex Program.
      enforce_dpp : bool, optional
          When True, a DPPError will be thrown when trying to parse a non-DPP
          problem (instead of just a warning). Defaults to False.
  
      Returns
      -------
      SolvingChain
          A SolvingChain that can be used to solve the problem.
  
      Raises
      ------
      SolverError
          Raised if no suitable solver exists among the installed solvers, or
          if the target solver is not installed.
      """
      if len(problem.variables()) == 0:
          return SolvingChain(reductions=[ConstantSolver()])
      reductions = _reductions_for_problem_class(problem, candidates, gp)
  
      dpp_context = 'dcp' if not gp else 'dgp'
      dpp_error_msg = (
              "You are solving a parameterized problem that is not DPP. "
              "Because the problem is not DPP, subsequent solves will not be "
              "faster than the first one. For more information, see the "
              "documentation on Discplined Parametrized Programming, at\n"
              "\thttps://www.cvxpy.org/tutorial/advanced/index.html#"
              "disciplined-parametrized-programming")
      if not problem.is_dpp(dpp_context):
          if not enforce_dpp:
              warnings.warn(dpp_error_msg)
              reductions = [EvalParams()] + reductions
          else:
              raise DPPError(dpp_error_msg)
      elif any(param.is_complex() for param in problem.parameters()):
          reductions = [EvalParams()] + reductions
  
      # Conclude with matrix stuffing; choose one of the following paths:
      #   (1) QpMatrixStuffing --> [a QpSolver],
      #   (2) ConeMatrixStuffing --> [a ConicSolver]
      if _solve_as_qp(problem, candidates):
          # Canonicalize as a QP
          solver = candidates['qp_solvers'][0]
          solver_instance = slv_def.SOLVER_MAP_QP[solver]
          reductions += [QpMatrixStuffing(),
                         solver_instance]
          return SolvingChain(reductions=reductions)
  
      # Canonicalize as a cone program
      if not candidates['conic_solvers']:
          raise SolverError("Problem could not be reduced to a QP, and no "
                            "conic solvers exist among candidate solvers "
                            "(%s)." % candidates)
  
      constr_types = set()
      # ^ We use constr_types to infer an incomplete list of cones that
      # the solver will need after canonicalization.
      for c in problem.constraints:
          constr_types.add(type(c))
      ex_cos = [ct for ct in constr_types if ct in EXOTIC_CONES]
      # ^ The way we populate "ex_cos" will need to change if and when
      # we have atoms that require exotic cones.
      for co in ex_cos:
          sim_cos = EXOTIC_CONES[co]  # get the set of required simple cones
          constr_types.update(sim_cos)
          constr_types.remove(co)
      # We now go over individual elementary cones support by CVXPY (
      # SOC, ExpCone, NonNeg, Zero, PSD, PowCone3D) and check if
      # they've appeared in constr_types or if the problem has an atom
      # requiring that cone.
      cones = []
      atoms = problem.atoms()
      if SOC in constr_types or any(atom in SOC_ATOMS for atom in atoms):
          cones.append(SOC)
      if ExpCone in constr_types or any(atom in EXP_ATOMS for atom in atoms):
          cones.append(ExpCone)
      if any(t in constr_types for t in [Inequality, NonPos, NonNeg]) \
              or any(atom in NONPOS_ATOMS for atom in atoms):
          cones.append(NonNeg)
      if Equality in constr_types or Zero in constr_types:
          cones.append(Zero)
      if PSD in constr_types \
              or any(atom in PSD_ATOMS for atom in atoms) \
              or any(v.is_psd() or v.is_nsd() for v in problem.variables()):
          cones.append(PSD)
      if PowCone3D in constr_types:
          # if we add in atoms that specifically use the 3D power cone
          # (rather than the ND power cone), then we'll need to check
          # for those atoms here as well.
          cones.append(PowCone3D)
  
      # Here, we make use of the observation that canonicalization only
      # increases the number of constraints in our problem.
      has_constr = len(cones) > 0 or len(problem.constraints) > 0
  
      for solver in candidates['conic_solvers']:
          solver_instance = slv_def.SOLVER_MAP_CONIC[solver]
          if (all(c in solver_instance.SUPPORTED_CONSTRAINTS for c in cones)
                  and (has_constr or not solver_instance.REQUIRES_CONSTR)):
              if ex_cos:
                  reductions.append(Exotic2Common())
              reductions += [ConeMatrixStuffing(), solver_instance]
              return SolvingChain(reductions=reductions)
  
      raise SolverError("Either candidate conic solvers (%s) do not support the "
                        "cones output by the problem (%s), or there are not "
                        "enough constraints in the problem." % (
                            candidates['conic_solvers'],
                            ", ".join([cone.__name__ for cone in cones])))
function_tokens: def construct_solving_chain ( problem , candidates , gp : bool = False , enforce_dpp : bool = False ) -> "SolvingChain" : if len ( problem . variables ( ) ) == 0 : return SolvingChain ( reductions = [ ConstantSolver ( ) ] ) reductions = _reductions_for_problem_class ( problem , candidates , gp ) dpp_context = 'dcp' if not gp else 'dgp' dpp_error_msg = ( "You are solving a parameterized problem that is not DPP. " "Because the problem is not DPP, subsequent solves will not be " "faster than the first one. For more information, see the " "documentation on Discplined Parametrized Programming, at\n" "\thttps://www.cvxpy.org/tutorial/advanced/index.html#" "disciplined-parametrized-programming" ) if not problem . is_dpp ( dpp_context ) : if not enforce_dpp : warnings . warn ( dpp_error_msg ) reductions = [ EvalParams ( ) ] + reductions else : raise DPPError ( dpp_error_msg ) elif any ( param . is_complex ( ) for param in problem . parameters ( ) ) : reductions = [ EvalParams ( ) ] + reductions # Conclude with matrix stuffing; choose one of the following paths: #   (1) QpMatrixStuffing --> [a QpSolver], #   (2) ConeMatrixStuffing --> [a ConicSolver] if _solve_as_qp ( problem , candidates ) : # Canonicalize as a QP solver = candidates [ 'qp_solvers' ] [ 0 ] solver_instance = slv_def . SOLVER_MAP_QP [ solver ] reductions += [ QpMatrixStuffing ( ) , solver_instance ] return SolvingChain ( reductions = reductions ) # Canonicalize as a cone program if not candidates [ 'conic_solvers' ] : raise SolverError ( "Problem could not be reduced to a QP, and no " "conic solvers exist among candidate solvers " "(%s)." % candidates ) constr_types = set ( ) # ^ We use constr_types to infer an incomplete list of cones that # the solver will need after canonicalization. for c in problem . constraints : constr_types . add ( type ( c ) ) ex_cos = [ ct for ct in constr_types if ct in EXOTIC_CONES ] # ^ The way we populate "ex_cos" will need to change if and when # we have atoms that require exotic cones. for co in ex_cos : sim_cos = EXOTIC_CONES [ co ] # get the set of required simple cones constr_types . update ( sim_cos ) constr_types . remove ( co ) # We now go over individual elementary cones support by CVXPY ( # SOC, ExpCone, NonNeg, Zero, PSD, PowCone3D) and check if # they've appeared in constr_types or if the problem has an atom # requiring that cone. cones = [ ] atoms = problem . atoms ( ) if SOC in constr_types or any ( atom in SOC_ATOMS for atom in atoms ) : cones . append ( SOC ) if ExpCone in constr_types or any ( atom in EXP_ATOMS for atom in atoms ) : cones . append ( ExpCone ) if any ( t in constr_types for t in [ Inequality , NonPos , NonNeg ] ) or any ( atom in NONPOS_ATOMS for atom in atoms ) : cones . append ( NonNeg ) if Equality in constr_types or Zero in constr_types : cones . append ( Zero ) if PSD in constr_types or any ( atom in PSD_ATOMS for atom in atoms ) or any ( v . is_psd ( ) or v . is_nsd ( ) for v in problem . variables ( ) ) : cones . append ( PSD ) if PowCone3D in constr_types : # if we add in atoms that specifically use the 3D power cone # (rather than the ND power cone), then we'll need to check # for those atoms here as well. cones . append ( PowCone3D ) # Here, we make use of the observation that canonicalization only # increases the number of constraints in our problem. has_constr = len ( cones ) > 0 or len ( problem . constraints ) > 0 for solver in candidates [ 'conic_solvers' ] : solver_instance = slv_def . SOLVER_MAP_CONIC [ solver ] if ( all ( c in solver_instance . SUPPORTED_CONSTRAINTS for c in cones ) and ( has_constr or not solver_instance . REQUIRES_CONSTR ) ) : if ex_cos : reductions . append ( Exotic2Common ( ) ) reductions += [ ConeMatrixStuffing ( ) , solver_instance ] return SolvingChain ( reductions = reductions ) raise SolverError ( "Either candidate conic solvers (%s) do not support the " "cones output by the problem (%s), or there are not " "enough constraints in the problem." % ( candidates [ 'conic_solvers' ] , ", " . join ( [ cone . __name__ for cone in cones ] ) ) )