Attempt to solve the phase.

def solve(self):
        """Attempt to solve the phase."""
        if self.status != SolverStatus.pending:
            return self

        scopes = self.scopes[:]
        failure_reason = None
        extractions = {}

        changed_scopes_i = self.changed_scopes_i.copy()

        def _create_phase(status=None):
            phase = copy.copy(self)
            phase.scopes = scopes
            phase.failure_reason = failure_reason
            phase.extractions = extractions
            phase.changed_scopes_i = set()

            if status is None:
                phase.status = (SolverStatus.solved if phase._is_solved()
                                else SolverStatus.exhausted)
            else:
                phase.status = status
            return phase

        # iteratively reduce until no more reductions possible
        while True:
            prev_num_scopes = len(scopes)
            widened_scopes_i = set()

            # iteratively extract until no more extractions possible
            while True:
                self.pr.subheader("EXTRACTING:")
                extracted_requests = []

                # perform all possible extractions
                with self.solver.timed(self.solver.extraction_time):
                    for i in range(len(scopes)):
                        while True:
                            scope_, extracted_request = scopes[i].extract()

                            if extracted_request:
                                extracted_requests.append(extracted_request)
                                k = (scopes[i].package_name, extracted_request.name)
                                extractions[k] = extracted_request
                                self.solver.extractions_count += 1
                                scopes[i] = scope_
                            else:
                                break

                if not extracted_requests:
                    break

                # simplify extractions (there may be overlaps)
                self.pr.subheader("MERGE-EXTRACTIONS:")
                extracted_requests = RequirementList(extracted_requests)

                if extracted_requests.conflict:  # extractions are in conflict
                    req1, req2 = extracted_requests.conflict
                    conflict = DependencyConflict(req1, req2)
                    failure_reason = DependencyConflicts([conflict])
                    return _create_phase(SolverStatus.failed)
                elif self.pr:
                    self.pr("merged extractions: %s", extracted_requests)

                # intersect extracted requests with current scopes
                self.pr.subheader("INTERSECTING:")
                req_fams = []

                with self.solver.timed(self.solver.intersection_test_time):
                    for i, scope in enumerate(scopes):
                        extracted_req = extracted_requests.get(scope.package_name)

                        if extracted_req is None:
                            continue

                        # perform the intersection
                        scope_ = scope.intersect(extracted_req.range)

                        req_fams.append(extracted_req.name)

                        if scope_ is None:
                            # the scope conflicted with the extraction
                            conflict = DependencyConflict(
                                extracted_req, scope.package_request)
                            failure_reason = DependencyConflicts([conflict])
                            return _create_phase(SolverStatus.failed)

                        if scope_ is not scope:
                            # the scope was narrowed because it intersected
                            # with an extraction
                            scopes[i] = scope_
                            changed_scopes_i.add(i)
                            self.solver.intersections_count += 1

                            # if the intersection caused a conflict scope to turn
                            # into a non-conflict scope, then it has to be reduced
                            # against all other scopes.
                            #
                            # In the very common case, if a scope changes then it
                            # has been narrowed, so there is no need to reduce it
                            # against other unchanged scopes. In this case however,
                            # the scope actually widens! For eg, '~foo-1' may be
                            # intersected with 'foo' to become 'foo-1', which might
                            # then reduce against existing scopes.
                            #
                            if scope.is_conflict and not scope_.is_conflict:
                                widened_scopes_i.add(i)

                # add new scopes
                new_extracted_reqs = [
                    x for x in extracted_requests.requirements
                    if x.name not in req_fams]

                if new_extracted_reqs:
                    self.pr.subheader("ADDING:")
                    #n = len(scopes)

                    for req in new_extracted_reqs:
                        scope = _PackageScope(req, solver=self.solver)
                        scopes.append(scope)
                        if self.pr:
                            self.pr("added %s", scope)

            num_scopes = len(scopes)

            # no further reductions to do
            if (num_scopes == prev_num_scopes) \
                    and not changed_scopes_i \
                    and not widened_scopes_i:
                break

            # iteratively reduce until no more reductions possible
            self.pr.subheader("REDUCING:")

            if not self.solver.optimised:
                # force reductions across all scopes
                changed_scopes_i = set(range(num_scopes))
                prev_num_scopes = num_scopes

            # create set of pending reductions from the list of changed scopes
            # and list of added scopes. We use a sorted set because the solver
            # must be deterministic, ie its behavior must always be the same for
            # a given solve. A normal set does not guarantee order.
            #
            # Each item is an (x, y) tuple, where scope[x] will reduce by
            # scope[y].package_request.
            #
            pending_reducts = SortedSet()
            all_scopes_i = range(num_scopes)
            added_scopes_i = range(prev_num_scopes, num_scopes)

            for x in range(prev_num_scopes):
                # existing scopes must reduce against changed scopes
                for y in changed_scopes_i:
                    if x != y:
                        pending_reducts.add((x, y))

                # existing scopes must reduce against newly added scopes
                for y in added_scopes_i:
                    pending_reducts.add((x, y))

            # newly added scopes must reduce against all other scopes
            for x in added_scopes_i:
                for y in all_scopes_i:
                    if x != y:
                        pending_reducts.add((x, y))

            # 'widened' scopes (see earlier comment in this func) must reduce
            # against all other scopes
            for x in widened_scopes_i:
                for y in all_scopes_i:
                    if x != y:
                        pending_reducts.add((x, y))

            # iteratively reduce until there are no more pending reductions.
            # Note that if a scope is reduced, then other scopes need to reduce
            # against it once again.
            with self.solver.timed(self.solver.reduction_test_time):
                while pending_reducts:
                    x, y = pending_reducts.pop()

                    new_scope, reductions = scopes[x].reduce_by(
                        scopes[y].package_request)

                    if new_scope is None:
                        failure_reason = TotalReduction(reductions)
                        return _create_phase(SolverStatus.failed)

                    elif new_scope is not scopes[x]:
                        scopes[x] = new_scope

                        # other scopes need to reduce against x again
                        for j in all_scopes_i:
                            if j != x:
                                pending_reducts.add((j, x))

            changed_scopes_i = set()

        return _create_phase()