def METHOD_NAME(self, op: Operator, n: int) -> Optional[Operator]:
    """
    Sample the grammar `g` starting from `g.start`, that is, choose one element at random for each possible choices.
    Parameters
    ----------
    op : Operator
        starting rule (e.g., `g.start`)
    n : int
        number of derivations
    Returns
    -------
    Optional[Operator]
    """
    if isinstance(op, BasePipeline):
        steps = op.steps_list()
        new_maybe_steps: List[Optional[Operator]] = [
            self.METHOD_NAME(sop, n) for sop in op.steps_list()
        ]
        if None not in new_maybe_steps:
            new_steps: List[Operator] = cast(List[Operator], new_maybe_steps)
            step_map = {steps[i]: new_steps[i] for i in range(len(steps))}
            new_edges = [(step_map[s], step_map[d]) for s, d in op.edges()]
            return make_pipeline_graph(new_steps, new_edges, True)
        else:
            return None
    if isinstance(op, OperatorChoice):
        # This choice does not need to be cryptographically secure or hard to predict
        return self.METHOD_NAME(random.choice(op.steps_list()), n)  # nosec
    if isinstance(op, NonTerminal):
        return self.METHOD_NAME(getattr(self, op.name()), n - 1) if n > 0 else None
    if isinstance(op, IndividualOp):
        return op
    assert False, f"Unknown operator {op}"