Project: s41467-024-51491-7 | Function: Analyzer.incremental_ERR (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:** Compute incremental energy release rate (ERR) for Mode I and Mode II fracture using Virtual Crack Closure Technique (VCCT). This function calculates the critical energy release rates G_I and G_II that determine fracture toughness in mixed-mode loading scenarios.

**KEY TERMS:** {
  "Ginc1": "Mode I energy release rate G_I",
  "Ginc2": "Mode II energy release rate G_II",
  "_integrand_GI": "Mode I integrand σ·ε·h",
  "_integrand_GII": "Mode II integrand τ·γ·h",
  "da": "incremental crack advance"
}

**CONTEXT CODE:**         ---------
        Z : ndarray
            Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
        sc : float
            Weak-layer compressive strength. Default is 2.6 kPa.
        unit : {'kPa', 'MPa'}, optional
            Desired output unit. Default is 'kPa'.
        val : str, optional
            Maximum 'max' or minimum 'min' principal stress. Default is 'min'.
        normalize : bool
            Toggle layerwise normalization to strength.

        Returns
        -------
        ndarray
            Maximum or minimum principal stress in specified unit.

        Raises
        ------
        ValueError
            If specified principal stress component is neither 'max' nor
            'min', or if normalization of tensile principal stress
            is requested.
        """
        # Raise error if specified component is not available
        if val not in ["min", "max"]:
            raise ValueError(f"Component {val} not defined.")

        # Multiplier selection dict
        m = {"max": 1, "min": -1}

        # Get weak-layer normal and shear stresses
        sig = self.sm.fq.sig(Z, unit=unit)
        tau = self.sm.fq.tau(Z, unit=unit)

        # Calculate principal stress
        ps = sig / 2 + m[val] * np.sqrt(sig**2 + 4 * tau**2) / 2

        # Raise error if normalization of tensile stresses is attempted
        if normalize and val == "max":
            raise ValueError("Can only normalize compressive stresses.")

        # Normalize compressive stresses to compressive strength
        if normalize and val == "min":
            return ps / sc

        # Return absolute principal stresses
        return ps

    @track_analyzer_call
    def incremental_ERR(
        self, tolerance: float = 1e-6, unit: Literal["kJ/m^2", "J/m^2"] = "kJ/m^2"
    ) -> np.ndarray:
        """
        Compute incremental energy release rate (ERR) of all cracks.

        Returns
        -------
        ndarray
            List of total, mode I, and mode II energy release rates.
        """
        # MASKED: Compute incremental energy release rate (ERR) for Mode I and Mode II fracture using Virtual Crack Closure Technique (VCCT). This function calculates the critical energy release rates G_I and G_II that determine fracture toughness in mixed-mode loading scenarios.
        raise NotImplementedError("Function has been masked for testing purposes")

    @track_analyzer_call
    def differential_ERR(
        self, unit: Literal["kJ/m^2", "J/m^2"] = "kJ/m^2"
    ) -> np.ndarray:
        """
        Compute differential energy release rate of all crack tips.

        Returns
        -------
        ndarray
            List of total, mode I, and mode II energy release rates.
        """
        li = self.sm.scenario.li
        ki = self.sm.scenario.ki
        C = self.sm.unknown_constants
        phi = self.sm.scenario.phi
        qs = self.sm.scenario.surface_load

        # Get number and indices of segment transitions
        ntr = len(li) - 1
        itr = np.arange(ntr)

        # Identify supported-free and free-supported transitions as crack tips
        iscracktip = [ki[j] != ki[j + 1] for j in range(ntr)]

        # Transition indices of crack tips and total number of crack tips
        ict = itr[iscracktip]
        nct = len(ict)

        # Initialize energy release rate array
        Gdif = np.zeros([3, nct])

        # Compute energy relase rate of all crack tips
        for j, idx in enumerate(ict):
            # Solution at crack tip
            z = self.sm.z(
                li[idx], C[:, [idx]], li[idx], phi, has_foundation=ki[idx], qs=qs
            )
            # Mode I and II differential energy release rates
            Gdif[1:, j] = np.concatenate(
                (self.sm.fq.Gi(z, unit=unit), self.sm.fq.Gii(z, unit=unit))
            )

        # Sum mode I and II contributions
        Gdif[0, :] = Gdif[1, :] + Gdif[2, :]

        # Adjust contributions for center cracks
        if nct > 1:
            avgmask = np.full(nct, True)  # Initialize mask
            avgmask[[0, -1]] = ki[[0, -1]]  # Do not weight edge cracks
            Gdif[:, avgmask] *= 0.5  # Weigth with half crack length

        # Return total differential energy release rate of all crack tips
        return Gdif.sum(axis=1)

    def _integrand_GI(
        self, x: float | np.ndarray, z_uncracked, z_cracked
    ) -> float | np.ndarray:
        """
        Mode I integrand for energy release rate calculation.
        """
        sig_uncracked = self.sm.fq.sig(z_uncracked(x))
        eps_cracked = self.sm.fq.eps(z_cracked(x))
        return sig_uncracked * eps_cracked * self.sm.weak_layer.h

    def _integrand_GII(
        self, x: float | np.ndarray, z_uncracked, z_cracked
    ) -> float | np.ndarray:
        """
        Mode II integrand for energy release rate calculation.
        """
        tau_uncracked = self.sm.fq.tau(z_uncracked(x))
        gamma_cracked = self.sm.fq.gamma(z_cracked(x))
        return tau_uncracked * gamma_cracked * self.sm.weak_layer.h

    @track_analyzer_call
    def total_potential(self):
        """
        Returns total differential potential.
        Currently only implemented for PST systems.

        Returns
        -------
        Pi : float
            Total differential potential (Nmm).
        """


Complete the function implementation replacing "raise NotImplementedError".