.. _plugins:

Plugins
=======

QDK/Chemistry uses a plugin system to support multiple implementations of each of the available :doc:`algorithm <algorithms/index>` type.
This allows switching between native QDK implementations and third-party packages (e.g., PySCF, Qiskit) without modifying application code.

.. _plugin-system:

Plugin system
-------------

.. _algorithm-plugin-relationship:

Architecture
~~~~~~~~~~~~

Each :doc:`algorithm <algorithms/index>` in QDK/Chemistry can have multiple implementations.
All implementations inherit from the same base class and conform to the same interface:

.. graphviz:: /_static/diagrams/interface_architecture.dot

This design supports several workflows:

- Benchmarking native implementations against established packages
- Mixing backends (e.g., PySCF for :term:`SCF`, :term:`MACIS` for multi-configurational methods)
- Adding custom implementations

The implementations for each algorithm type are managed by a :doc:`factory class <algorithms/factory_pattern>`, which provides a consistent interface for creating instances and listing available implementations.
We refer the reader to the :doc:`factory pattern <algorithms/factory_pattern>` and :doc:`algorithm <algorithms/index>` documentation pages for more details on this design pattern.


Using plugins
~~~~~~~~~~~~~

To select an implementation, specify it by name:

.. tab:: Python API

   .. literalinclude:: ../../_static/examples/python/interfaces.py
      :language: python
      :start-after: # start-cell-scf
      :end-before: # end-cell-scf

.. tab:: C++ API

   .. literalinclude:: ../../_static/examples/cpp/interfaces.cpp
      :language: cpp
      :start-after: // start-cell-scf
      :end-before: // end-cell-scf

.. _listing-implementations:

To list available implementations:

.. tab:: Python API

   .. literalinclude:: ../../_static/examples/python/interfaces.py
      :language: python
      :start-after: # start-cell-list-methods
      :end-before: # end-cell-list-methods

.. tab:: C++ API

   .. literalinclude:: ../../_static/examples/cpp/interfaces.cpp
      :language: cpp
      :start-after: // start-cell-list-methods
      :end-before: // end-cell-list-methods

Documentation pertaining to the availability and configuration of each algorithm implementation provided within QDK/Chemistry can be found on the :doc:`algorithm <algorithms/index>` documentation pages.



Included third-party plugins
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In addition to the native implementations packaged within QDK/Chemistry, plugins are included for the following packages:

- `PySCF <https://pyscf.org/>`_ — Python-based quantum chemistry
- `Qiskit <https://www.ibm.com/quantum/qiskit>`_ — Quantum algorithm primitives
- `OpenFermion <https://quantumai.google/openfermion>`_ — Quantum algorithm primitives
- `geomeTRIC <https://github.com/leeping/geomeTRIC>`_ — Molecular geometry optimization

These plugins are enabled automatically when the corresponding package is installed.

.. _qiskit-plugin-details:

Qiskit plugin details
^^^^^^^^^^^^^^^^^^^^^

The Qiskit plugin uses **opportunistic loading** to maximize compatibility across different installation configurations.
When Qiskit is installed, the plugin will load and register the available algorithms. The optional ecosystem packages (Qiskit Aer and Qiskit Nature) are loaded based on their availability in your environment.

**Loading behavior:**

- **Qiskit (core)**: Loaded when the plugin is initialized and Qiskit is installed.
- **Qiskit Nature**: Loaded if ``qiskit-nature`` is installed.
- **Qiskit Aer**: Loaded if ``qiskit-aer`` is installed.

**Installing optional Qiskit packages:**

To install the optional Qiskit ecosystem packages, use the ``qiskit-extras`` extra when installing QDK/Chemistry:

.. code-block:: bash

   pip install 'qdk-chemistry[qiskit-extras]'

Alternatively, you can install them directly:

.. code-block:: bash

   pip install qiskit-aer qiskit-nature

.. note::

   On Python 3.14, ``qiskit-aer`` is omitted from ``qiskit-extras`` on Linux ARM64
   (aarch64), because Qiskit does not yet publish a Python 3.14 wheel for that
   platform. All other platforms install the full set.

**Checking what is loaded:**

To determine which Qiskit components are available in your environment, you can check the following module-level variables:

.. code-block:: python

   from qdk_chemistry.plugins.qiskit import (
       QDK_CHEMISTRY_HAS_QISKIT,
       QDK_CHEMISTRY_HAS_QISKIT_NATURE,
       QDK_CHEMISTRY_HAS_QISKIT_AER,
   )

   print(f"Qiskit core available: {QDK_CHEMISTRY_HAS_QISKIT}")
   print(f"Qiskit Nature available: {QDK_CHEMISTRY_HAS_QISKIT_NATURE}")
   print(f"Qiskit Aer available: {QDK_CHEMISTRY_HAS_QISKIT_AER}")

These boolean variables are set at module load time and reflect the actual availability of each package in your Python environment.

.. warning::

   If you attempt to use an algorithm that requires an optional Qiskit package that is not installed,
   the algorithm will not be available in the factory. Use the :ref:`listing-implementations` pattern
   to see which implementations are currently available.

.. _openfermion-plugin-details:

OpenFermion plugin details
^^^^^^^^^^^^^^^^^^^^^^^^^^
The OpenFermion plugin integrates QDK/Chemistry with `OpenFermion <https://quantumai.google/openfermion>`_.
Like the Qiskit plugin, it uses **opportunistic loading**: the plugin loads when OpenFermion is installed.

**Loading behavior:**

- **OpenFermion**: Loaded when the plugin is initialized and OpenFermion is installed.

**Installing OpenFermion packages:**

To install OpenFermion, use the ``openfermion-extras`` extra when installing QDK/Chemistry:

.. code-block:: bash

   pip install 'qdk-chemistry[openfermion-extras]'

**Checking what is loaded:**

To determine which OpenFermion components are available in your environment, you can check the following module-level variables:

.. code-block:: python

   from qdk_chemistry.plugins.openfermion import (
       QDK_CHEMISTRY_HAS_OPENFERMION,
   )

   print(f"OpenFermion available: {QDK_CHEMISTRY_HAS_OPENFERMION}")

This boolean variable is set at module load time and reflects the actual availability of each package in your Python environment.

.. warning::

   If you attempt to use an algorithm that requires OpenFermion but the package is not installed,
   the algorithm will not be available in the factory. Use the :ref:`listing-implementations` pattern
   to see which implementations are currently available.

.. _community-plugins:

Community-developed plugins are also welcome. See :ref:`adding-plugins` for guidance on creating new plugins.

.. _adding-plugins:

Creating plugins
----------------

QDK/Chemistry supports two extension mechanisms:

1. Implementing a new backend for an existing algorithm type (e.g., integrating an external quantum chemistry package)
2. Defining an entirely new algorithm type with its own factory and implementations

The following sections provide comprehensive examples of each approach.

.. _adding-implementations:

Implementing a new algorithm backend
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This section demonstrates how to integrate an external :term:`SCF` solver as a QDK/Chemistry plugin, enabling access through the standard API.

.. rubric:: Interface requirements

Each algorithm type in QDK/Chemistry defines an abstract base class specifying the interface that all implementations must satisfy:

- A ``name()`` method that returns a unique identifier for the implementation
- A ``_run_impl()`` method containing the computational logic
- A ``settings()`` object for runtime configuration

.. rubric:: Defining custom settings

When an implementation requires configuration options beyond those provided by the base settings class, a derived settings class can be defined:

.. tab:: Python API

   .. literalinclude:: ../../_static/examples/python/custom_plugin.py
      :language: python
      :start-after: # start-cell-custom-settings
      :end-before: # end-cell-custom-settings

.. tab:: C++ API

   .. literalinclude:: ../../_static/examples/cpp/custom_plugin.cpp
      :language: cpp
      :start-after: // start-cell-custom-settings
      :end-before: // end-cell-custom-settings

.. rubric:: Implementation structure

The implementation class inherits from the algorithm base class and overrides the required methods.
The ``_run_impl()`` method is responsible for:

1. Converting QDK/Chemistry data structures to the external package's format
2. Invoking the external computation
3. Converting results back to QDK/Chemistry data structures

.. tab:: Python API

   .. literalinclude:: ../../_static/examples/python/custom_plugin.py
      :language: python
      :start-after: # start-cell-custom-scf-solver
      :end-before: # end-cell-custom-scf-solver

.. tab:: C++ API

   .. literalinclude:: ../../_static/examples/cpp/custom_plugin.cpp
      :language: cpp
      :start-after: // start-cell-custom-scf-solver
      :end-before: // end-cell-custom-scf-solver

.. rubric:: Registration

Implementations are registered with the algorithm factory to enable discovery and instantiation by name.
Registration is typically performed during module initialization:

.. tab:: Python API

   .. literalinclude:: ../../_static/examples/python/custom_plugin.py
      :language: python
      :start-after: # start-cell-registration
      :end-before: # end-cell-registration

.. tab:: C++ API

   .. literalinclude:: ../../_static/examples/cpp/custom_plugin.cpp
      :language: cpp
      :start-after: // start-cell-registration
      :end-before: // end-cell-registration

Following registration, the implementation is accessible through the standard API:

.. literalinclude:: ../../_static/examples/python/custom_plugin.py
   :language: python
   :start-after: # start-cell-usage-after-registration
   :end-before: # end-cell-usage-after-registration

.. _custom-algorithm-types:

Defining a new algorithm type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When the required functionality does not correspond to an existing algorithm category, a new algorithm type can be defined.
This section demonstrates the complete process using a molecular descriptor calculator as an example.

.. rubric:: Interface design

The first step is to specify the algorithm's interface:

Input type
   The data the algorithm operates on (e.g., ``Structure``)
Output type
   The data the algorithm produces (e.g., a floating-point molecular descriptor)
Configuration
   Required settings (e.g., whether to normalize the descriptor)

.. rubric:: Settings class definition

Define a settings class containing all configuration parameters:

.. tab:: Python API

   .. literalinclude:: ../../_static/examples/python/custom_plugin.py
      :language: python
      :start-after: # start-cell-descriptor-settings
      :end-before: # end-cell-descriptor-settings

.. tab:: C++ API

   .. literalinclude:: ../../_static/examples/cpp/custom_plugin.cpp
      :language: cpp
      :start-after: // start-cell-descriptor-settings
      :end-before: // end-cell-descriptor-settings

.. rubric:: Base class definition

Define an abstract base class specifying the interface for all implementations:

.. tab:: Python API

   .. literalinclude:: ../../_static/examples/python/custom_plugin.py
      :language: python
      :start-after: # start-cell-descriptor-base-class
      :end-before: # end-cell-descriptor-base-class

.. tab:: C++ API

   .. literalinclude:: ../../_static/examples/cpp/custom_plugin.cpp
      :language: cpp
      :start-after: // start-cell-descriptor-base-class
      :end-before: // end-cell-descriptor-base-class

.. rubric:: Factory definition

The factory manages implementation registration and provides instance creation:

.. tab:: Python API

   .. literalinclude:: ../../_static/examples/python/custom_plugin.py
      :language: python
      :start-after: # start-cell-descriptor-factory
      :end-before: # end-cell-descriptor-factory

.. tab:: C++ API

   .. literalinclude:: ../../_static/examples/cpp/custom_plugin.cpp
      :language: cpp
      :start-after: // start-cell-descriptor-factory
      :end-before: // end-cell-descriptor-factory

.. rubric:: Concrete implementations

Implement the algorithm by inheriting from the base class:

.. tab:: Python API

   .. literalinclude:: ../../_static/examples/python/custom_plugin.py
      :language: python
      :start-after: # start-cell-descriptor-implementations
      :end-before: # end-cell-descriptor-implementations

.. tab:: C++ API

   .. literalinclude:: ../../_static/examples/cpp/custom_plugin.cpp
      :language: cpp
      :start-after: // start-cell-descriptor-implementations
      :end-before: // end-cell-descriptor-implementations

Additional implementations follow the same pattern:

.. literalinclude:: ../../_static/examples/python/custom_plugin.py
   :language: python
   :start-after: # start-cell-mass-descriptor
   :end-before: # end-cell-mass-descriptor

.. rubric:: Registration

Register the factory and all implementations:

.. tab:: Python API

   .. literalinclude:: ../../_static/examples/python/custom_plugin.py
      :language: python
      :start-after: # start-cell-descriptor-registration
      :end-before: # end-cell-descriptor-registration

.. tab:: C++ API

   .. literalinclude:: ../../_static/examples/cpp/custom_plugin.cpp
      :language: cpp
      :start-after: // start-cell-descriptor-registration
      :end-before: // end-cell-descriptor-registration

.. rubric:: Usage

Following registration, the new algorithm type is accessible through the standard API:

.. literalinclude:: ../../_static/examples/python/custom_plugin.py
   :language: python
   :start-after: # start-cell-descriptor-usage
   :end-before: # end-cell-descriptor-usage

For additional information on the factory pattern and settings system, refer to the
:doc:`factory pattern <algorithms/factory_pattern>` and :doc:`settings <algorithms/settings>` documentation.


Further reading
---------------

- Custom plugin examples: `C++ source <../../_static/examples/cpp/custom_plugin.cpp>`__ | `Python source <../../_static/examples/python/custom_plugin.py>`__
- Plugin usage examples: `C++ example <../../_static/examples/cpp/interfaces.cpp>`__ | `Python example <../../_static/examples/python/interfaces.py>`__
- :doc:`Factory pattern <algorithms/factory_pattern>`
- :doc:`Settings <algorithms/settings>`
- :doc:`Serialization <data/serialization>`
