//  [python]
def solve(self, trace=False, iterlimit=None):
        x = torch.zeros((self.nbatch, self.dim, 1), dtype=torch.float32, device=self.Q.device)
        if trace:
            trace = [x]
        working_set = torch.zeros((self.nbatch, self.nc, 1), dtype=torch.bool, device=x.device)
        converged = torch.zeros((self.nbatch, 1, 1), dtype=torch.bool, device=x.device)

        if iterlimit is None:
            iterlimit = 10 * self.nc

        iterations = 0
        while not torch.all(converged):
            A = self.G * working_set
            b = self.h * working_set

            g = self.Q.bmm(x) + self.p
            h = A.bmm(x) - b

            AT = A.transpose(1, 2)
            AQinvAT = A.bmm(torch.linalg.solve(self.Q, AT))
            rhs = h - A.bmm(torch.linalg.solve(self.Q, g))
            diag = torch.diag_embed(~working_set.squeeze(dim=2))
            lmbd = torch.linalg.solve(AQinvAT + diag, rhs)
            p = torch.linalg.solve(self.Q, -AT.bmm(lmbd) - g)

            at_ws_sol = torch.linalg.norm(p, dim=1, keepdim=True) < 1e-3

            lmbdmin, lmbdmin_idx = torch.min(
                torch.where(
                    at_ws_sol & working_set,
                    lmbd,
                    torch.inf,
                ),
                dim=1,
                keepdim=True,
            )

            converged = converged | (at_ws_sol & (lmbdmin >= 0))

            # remove inverted constraints from working set
            working_set = working_set.scatter(
                1,
                lmbdmin_idx,
                (~at_ws_sol | (lmbdmin >= 0)) & working_set.gather(1, lmbdmin_idx),
            )

            # check constraint violations
            mask = ~at_ws_sol & ~working_set & (self.G.bmm(p) > 0)
            alpha, alpha_idx = torch.min(
                torch.where(
                    mask,
                    (self.h - self.G.bmm(x)) / (self.G.bmm(p)),
                    torch.inf,
                ),
                dim=1,
                keepdim=True,
            )

            # add violated constraints to working set
            working_set = working_set.scatter(
                1,
                alpha_idx,
                working_set.gather(1, alpha_idx) | (~at_ws_sol & (alpha < 1)),
            )
            alpha = torch.clamp(alpha, max=1)

            # update solution
            x = x + alpha * p * (~at_ws_sol & ~converged)
            if trace:
                trace.append(x.squeeze(2))

            iterations += 1
            if iterations > iterlimit:
                # Qs = self.Q[~converged.flatten()]
                # ps = self.p[~converged.flatten()]
                # Gs = self.G[~converged.flatten()]
                # hs = self.h[~converged.flatten()]
                # xs = x[~converged.flatten()]
                # for i in range(torch.sum(~converged)):
                #     print(f"Qs[{i}]:\n{Qs[i]}")
                #     print(f"ps[{i}]:\n{ps[i]}")
                #     print(f"Gs[{i}]:\n{Gs[i]}")
                #     print(f"hs[{i}]:\n{hs[i]}")
                #     print(f"xs[{i}]:\n{xs[i]}")
                raise RuntimeError(
                    f"Failed to converge in {iterlimit} iterations\n\n{torch.sum(~converged).item()} out of"
                    f" {self.nbatch} not converged"
                )

        if trace:
            return x.squeeze(2), trace

        return x.squeeze(2)