//  [python]
def trf_no_bounds(self, func, jac, x0, f0, J0, 
                      ftol, xtol, gtol, max_nfev, loss_function):
        x = x0.clone()
        f = f0
        f_true = f.clone()
        nfev = 1

        J = J0
        njev = 1
        m, n = J.shape

        if loss_function is not None:
            rho = loss_function(f)
            cost = 0.5 * torch.sum(rho[0])
            J, f = self.scale_for_robust_loss_function(J, f, rho)
        else:
            cost = 0.5 * torch.dot(f, f)
        
        g = torch.einsum('ij, j->i', J.T, f) # J.T.dot(f) calculate the grad
        
        # before update, we need to compute variables scale based on the jacobian 
        scale_inv = torch.sum(J**2, dim=0) ** 0.5
        scale_inv[scale_inv == 0] = 1
        scale = 1 / scale_inv

        Delta = torch.linalg.norm(x0 * scale_inv)
        if Delta == 0:
            Delta = torch.tensor(1., dtype=self.dtype, device=self.device)

        if max_nfev is None:
            max_nfev = n * 100 # 100 multiply the size of x0
        
        alpha = 0.0 # "Levenberg-Marquardt" parameter, also called damped factor

        termination_status = None
        iteration = 0
        step_norm = None
        actual_reduction = None

        while True:
            g_norm = torch.max(torch.abs(g))
            if g_norm < gtol:
                termination_status = 1

            if termination_status is not None or nfev == max_nfev:
                break

            d = scale
            g_h = d * g

            # solve with exact method because our jacobian is dense descriped
            J_h = J * d
            U, s, V = torch.linalg.svd(J_h, full_matrices=False)
            V = V.T
            uf = torch.einsum('ij, j->i', U.T, f)
            
            actual_reduction = -1
            while actual_reduction <=0 and nfev < max_nfev:
                # Solve a trust-region problem in least-squares minimization
                step_h, alpha, n_iter = self.solve_lsq_trust_region(
                    n, m, uf, s, V, Delta, initial_alpha=alpha
                )

                # compute values of a quadratic function in least squares
                predicted_reduction = -self.evaluate_quadratic(J_h, g_h, step_h)
                step = d * step_h
                x_new = x + step
                f_new = func(x_new)
                nfev += 1

                step_h_norm = torch.linalg.norm(step_h)

                if not torch.all(torch.isfinite(f_new)):
                    Delta = 0.25 * step_h_norm
                    continue    

                cost_new = 0.5 * torch.dot(f_new, f_new)
                actual_reduction = cost - cost_new

                Delta_new, ratio = self.update_tr_radius(
                    Delta, actual_reduction, predicted_reduction,
                    step_h_norm, step_h_norm > 0.95 * Delta)
                
                step_norm = torch.linalg.norm(step)
                termination_status = self.check_termination(
                    actual_reduction, cost, step_norm, torch.linalg.norm(x), ratio, ftol, xtol)

                if termination_status is not None:
                    break
                
                alpha *= Delta / Delta_new
                Delta = Delta_new

            # check the validation of solution if actual reduction larger than 0
            if actual_reduction > 0:
                x = x_new
                f = f_new
                f_true = f.clone()

                cost = cost_new

                J = jac(x, f)
                njev += 1

                if loss_function is not None:
                    rho = loss_function(f)
                    J, f = self.scale_for_robust_loss_function(J, f, rho)

                g = torch.einsum('ij, j->i', J.T, f)

                scale_inv = torch.sum(J**2, dim=0) ** 0.5
                scale_inv[scale_inv == 0] = 1
                scale = 1 / scale_inv
                # save the system configuration
                self.system.save_to_json('./lens_file/' + self.system.LensName +
                                         datetime.now().strftime('_%y%m%d_%H%M%S') + 
                                         '.json')

            else:
                step_norm = 0
                actual_reduction = 0

            # return the optmized solution in this situation
            print('<iter>: {:4d}, merit func norm: {:.8e}, cost: {:.8e}, \
                  first-order optimality {:.4e}, termination status: {}'
                  .format(iteration, torch.linalg.norm(f_true).item(), cost.item(), 
                          g_norm.item(), termination_status))
            iteration += 1

        return {'nfev': nfev, 'cost': cost.item(), 'optimality': g_norm.item()}