Project: s41467-024-45642-z | Function: __init__ (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:** Initialize Bloch wave calculation for systematic row conditions with multi-beam dynamical diffraction

**KEY TERMS:** {
  "crystal": "crystal structure",
  "beamlist": "diffracted beams",
  "q_dir_hkl": "tilt direction",
  "voltage": "acceleration voltage",
  "wavevector": "electron wave vector",
  "foilnormal": "foil normal direction",
  "potential": "crystal potential"
}

**CONTEXT CODE:**     
    // Brute force parameters and their range (Min and Max, use null for range found in calculation)
    "brute_force_parameters": {
        "thickness(nm)": [120.0, 140.0],
        "depth(nm)": [40.0, 80.0],
    },
    
    // Labels for brute force plots
    //"brute_force_labels": [
    //    "$\\mathrm{Thickness}\\,t\\,(\\mathrm{nm})$",
    //    "$\\mathrm{Depth}\\,d\\,(\\mathrm{nm})$" 
    //],
    
    // Only brute force depths less equal to thickness (defaults to true)
    //"constrain_depth_thickness": true,
    
    // Which parameters to automatically optimize
    "optimized_parameters": ["tilt(1/nm)", "x_shift(nm)", "q_shift(1/nm)"],
    
    // MTF to use along q direction
    "mtf": [["GAUSSIAN", 0.738033, 102.968], ["LORENTZIAN", 0.029029, 0.117078]],
    
    // Whether background is subtracted (defaults to False)
    //"subtract_background": false,
    
    // Acceleration voltage in kV (otherwise taken from experiment)
    "acceleration_voltage(kV)": 300.0
}"""


ParameterRange = Union[Tuple[Optional[float], Optional[float]], None]


def make_axis_name(axis: Axis, n: int) -> str:
    """Return name for given Axis *axis* with index *n*"""
    name = axis.name if axis.name else f"axis-{n}"
    unit = getattr(axis, "unit", None)
    if unit:
        name += f"({unit})"
    return name


class DataSetMetadataWrapper(BaseDataSet):
    """
    Override metadata of original dataset

    :param source: Source dataset
    :param axes: Overriden axes
    :param metadata: Overriden metadata
    """
    def __init__(self, source: DataSet, axes: Tuple[Axis, ...] = None, metadata: Optional[CoreMetaData] = None):
        if axes is None:
            axes = ()
        if len(axes) > source.ndim:
            raise ValueError("Too many axes in *axes*.")
        axes += source.axes[len(axes):]

        if metadata is None:
            metadata = source.metadata

        super().__init__(shape=source.shape, dtype=source.dtype, axes=axes, metadata=metadata)
        self._source = source

    def get(self, *args, **kw):
        return self._source.get(*args, **kw)

    def set(self, *args, **kw):
        self._source.set(*args, **kw)


class QXRenderer:
    """
    Creates QX-plots from Multibeam-Data.

    :param beamlist: Beam-List, also defines crystal coordinates
    :param shape: Shape of output
    :param x_axis: Describes X-axis of output
    :param q_axis: Describes Q-axis of output
    :param q_dir_hkl: Direction of tilt axis in crystal coordinates
    :param voltage: Acceleration voltage in kV
    :param dtype: Data type of output
    :param method: Interpolation method (see scipy.interpolate.interpn)
    """
    def __init__(self, beamlist: BeamList, shape: Tuple[int, int], x_axis: LinearAxis, q_axis: LinearAxis,
                 q_dir_hkl: ArrayLike, voltage: float, dtype: DTypeLike = None, method="linear"):
        self._beamlist = beamlist

        if len(shape) != 2:
            raise ValueError("Expected *shape* to have two components.")
        self._shape = tuple(shape)

        if x_axis.unit != 'nm' or x_axis.context != 'POSITION':
            raise ValueError("Expected *x_axis* to have unit 'nm' and context 'POSITION'.")
        self._x_axis = x_axis

        if q_axis.unit != '1/nm' or q_axis.context != 'DIFFRACTION':
            raise ValueError("Expected *q_axis* to have unit '1/nm' and context 'DIFFRACTION'.")
        self._q_axis = q_axis

        q_dir_hkl = np.array(q_dir_hkl)
        if q_dir_hkl.shape != (3,):
            raise ValueError("Expected 'q_dir_hkl' to have shape (3,).")
        q_dir = self._beamlist.coord.map_rcpr_to_cartesian(q_dir_hkl)
        self._q_dir = q_dir / np.sqrt(np.sum(q_dir ** 2))

        self.dtype = np.dtype(dtype if dtype is not None else np.float32)
        self.method = method
        self.wavelength = calc_wavelength(voltage)

    @property
    def beamlist(self) -> BeamList:
        return self._beamlist

    @property
    def shape(self) -> Tuple[int, ...]:
        """Shape of the output"""
        return self._shape

    @property
    def axes(self) -> Tuple[Axis, ...]:
        """Axes of the output"""
        return (self._x_axis, self._q_axis)

    @property
    def q_dir(self) -> np.dtype:
        """Direction of Q-axis in crystal space"""
        return self._q_dir

    def __call__(self, semi_conv: float, tilt: float, source: DataSet, defocus: float = 0.0, curvature: float = 0.0) -> DataSet:
        """
        Create QX plot from 'source_data'.

        :param semi_conv: semi convergence angle (1/nm)
        :param tilt: Tilt offset (1/nm)
        :param source: Data source with axes: (beam-index, x-axis, tilt-axis)
        :param defocus: Defocus (nm)
        :param curvature: Curvature (1/nm2)
        """
        semi_conv = float(semi_conv)
        defocus = float(defocus)
        curvature = float(curvature)
        if semi_conv < 0.5 * abs(self._q_axis.scale):
            raise ValueError("Convergence angle must be larger than Q-axis scaling")

        if source.ndim != 3:
            raise ValueError("Expected 'source_data' to be 3 dimensional")
        if source.shape[0] != len(self._beamlist):
            raise ValueError("Expected dimension #0 to correspond to beam list length.")
        if source.axes[0].context != 'INDEX':
            raise ValueError("Expected axis #0 to have *context* 'INDEX'.")


Complete the function implementation replacing "raise NotImplementedError".