def test_manual_optimization_with_non_pytorch_scheduler(automatic_optimization):
    """In manual optimization, the user can provide a custom scheduler that doesn't follow PyTorch's interface."""

    class IncompatibleScheduler:
        def __init__(self, optimizer):
            self.optimizer = optimizer

        def state_dict(self):
            return {}

        def load_state_dict(self, _):
            pass

    class Model(BoringModel):
        def __init__(self):
            super().__init__()
            self.automatic_optimization = automatic_optimization

        def configure_optimizers(self):
            optimizer = torch.optim.SGD(self.layer.parameters(), lr=0.1)
            scheduler = IncompatibleScheduler(optimizer)
            return [optimizer], [scheduler]

    model = Model()
    trainer = Trainer(accelerator="cpu", max_epochs=0, logger=False, enable_checkpointing=False)
    if automatic_optimization:
        with pytest.raises(MisconfigurationException, match="doesn't follow PyTorch's LRScheduler"):
            trainer.fit(model)
    else:
        # No error for manual optimization
        trainer.fit(model)
In manual optimization, the user can provide a custom scheduler that doesn't follow PyTorch's interface.
tests/tests_pytorch/trainer/optimization/test_manual_optimization.py