Project: s41467-024-51560-x | Function: _compute_error (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Computes the relative error between two field estimates for adaptive step size control in the nonlinear optical propagation solver, essential for maintaining numerical accuracy in the χ^(2) harmonic generation simulations

**KEY TERMS:** {
  "field_1": "reference field solution A_ref",
  "field_2": "test field solution A_test",
  "numbanorm": "L2 norm calculation ||·||",
  "error": "relative error estimate ε"
}

**CONTEXT CODE:**         adaptive_factor : float, optional
            Adaptive factor for the step size control. Bigger values lead to
            more aggressive step size control. The default is 1.1.
        max_dz : float, optional
            Maximum step size allowed. Particularly useful when simulating
            quasi-phase matched structures. The default is None.
        method: string
            Method used for integration. Can be RK4IP, ERK4IP or DP5IP.
            Default is DP5IP.
        breakpoints: array
            This array contains z coordinates where the solver will be forced
            to compute a result. Useful for poled structures, to avoid having
            a step larger than the poling. The default is an empty array.
        """
        self.model = model
        self.z_pts = z_pts
        self.local_error = local_error
        self.adaptive_factor = adaptive_factor
        self.max_dz = max_dz
        self.z_save = np.linspace(0, self.model.waveguide.length, self.z_pts)
        self.method_name = self._set_method_name(method)
        self.breakpoints = breakpoints
        self.stepsize_save = []
        self.power_prefactor = eps_0*c/2/self.model.waveguide.t_pts*self.model.waveguide.delta_t
    
    def _set_method_name(self, method):
        """
        Helper function to choose an appropriate solver. If a method is not
        provided by the user, it  will look into the model used to see if
        there is a recommended solver. Otherwise, defaults to the model that
        should be mostly used, i.e. DP5IP.

        Parameters
        ----------
        method : string
            Name of the solver.

        Returns
        -------
        string
            Name of the solver.
        """
        if method is None:
            if hasattr(self.model, '_recommended_solver'):
                return self.model._recommended_solver
            else:
                return 'DP5IP'
        else:
            return method
        
    def _compute_error(self, field_1, field_2):
        """
        Error estimation for adaptive step size

        Parameters
        ----------
        field_1 : array
            Reference field.
        field_2 : array
            Test field.

        Returns
        -------
        float
            Error estimate between the two fields.
        """
        raise NotImplementedError("Function implementation has been masked")
    
    def _dormand_prince_step(self, field, dz):
        """
        Dormand Prince order 5(4) adaptive scheme in the interaction picture. This
        new stepper uses the interaction picture in an embedded scheme of order 5.
        Tests have shown it to be up to four times faster than RK4IP.

        Parameters
        ----------
        field : array
            Current field.
        dz : float
            Current step size.

        Returns
        -------
        array
            New field estimate.
        
        float
            Error estimate.
        """
        raise NotImplementedError("Function implementation has been masked")
    
    def _rk546m(self, field, dz):
        """
        Some other Dormand-Prince method that has one less step but does not
        leverage the interaction picture and does not have the FSAL property.
        Usually worse than the other dormand-prince method.
        References:
            DOI: 10.1016/0771-050X(80)90013-3

        Parameters
        ----------
        field : array
            Current field.
        dz : float
            Current step size.

        Returns
        -------
        array
            New field estimate.
        
        float
            Error estimate.
        """
        raise NotImplementedError("Function implementation has been masked")
    
    def _adams_bashforth54_step(self, field, dz):
        """To be implemented"""
        pass

    def _rk4ip_single_step(self, field, dz):
        """
        Runge-Kutta order 4 step in the interaction picture (RK4IP)

        Parameters
        ----------
        field : array
            Current field.
        dz : float
            Current step size.

        Returns
        -------
        array
            New field estimate.
        """
        dispersion_op = numbaexp(-1j*self.model.waveguide.k*dz/2)
        a_int = dispersion_op*field
        k_1 = dz*dispersion_op*self.model.nonlinear_term(field)
        k_2 = dz*self.model.nonlinear_term(a_int + k_1/2)
        k_3 = dz*self.model.nonlinear_term(a_int + k_2/2)
        k_4 = dz*self.model.nonlinear_term(dispersion_op*(a_int + k_3))
        return dispersion_op*(a_int + k_1/6 + k_2/3 + k_3/3) + k_4/6
    
    def _rk4ip_step(self, field, dz):
        """
        Runge-Kutta order 4 adaptive scheme in the interaction picture (RK4IP)
        with step doubling and combining. Provided for comparison but slower
        than the dormand-prince method.
        References:


Complete the function implementation replacing "raise NotImplementedError".