.. _advanced:

===============
Advanced Usage
===============


Additional inputs to solving routines
---------------------------------------

Specifiying the Number of Variables Explicitly
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``num_variables`` is an optional parameter to
:meth:`eqc_direct.client.EqcClient.solve_integer` and :meth:`eqc_direct.client.EqcClient.solve_integer`, which defaults to the number of
variables inferred from the ``poly_indices`` argument, i.e., the largest integer
appearing over all the indices. However, a user may specify additional variables that do
not appear explicitly in ``poly_indices``. Typically, this is only one additional
variable, which creates `slack` with respect to the ``sum_constraint``, and whose value
does not affect the value of the objective function.

As a simple example, if ``num_variables=3`` and ``sum_constraint=1`` so that 

.. math::
    x_1 + x_2 + x_3 = 1,

with the objective function given as

.. math::
    f(X) = x_1^2 + x_2^2,

then :math:`x_1 = 0`, :math:`x_2 = 0`, and :math:`x_3 = 1` is a viable solution (giving
the ground-state energy :math:`f(X) = 0`). However, if :math:`x_3` were omitted by using
the default ``num_variables=2``, then :math:`x_1 = 0` and :math:`x_2 = 0` would not be a
viable solution because it would violate the corresponding sum constraint

.. math::
    x_1 + x_2 = 1.

Lastly, due to domain restriction :math:`x_3 \geq 0`, :math:`x_3` can only take up so
much slack in the problem, so that the subdomain for :math:`x_1` and :math:`x_2` is
enlarged but still limited.

Specifiying More than One Sample
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The Dirac-3 device is a stochastic solver, and furthermore some problems have multiple solutions that give the same minimum energy. Both of these considerations may indicate the need to run multiple samples that solve the same problem repeatedly on the device. ``num_samples`` is an optional parameter to both :meth:`eqc_direct.client.EqcClient.solve_integer` and :meth:`eqc_direct.client.EqcClient.solve_integer`, which defaults to one sample. Pass an integer greater than one to run multiple samples more efficiently as a "batch" in a single job. Additionally, running multiple samples in a single batch can save some time in pre-processing since it is only necessary to apply the formatting to submit the problem to the device a single time.

Device Configurations
^^^^^^^^^^^^^^^^^^^^^^^


Dirac-3 allows users to control the optimization process using the :code:`relaxation schedule` parameter. This parameter controls the time allocated for the system to evolve and converge towards the ground state and is available for both solver types.

Schedules 1, 2, 3, and 4 correspond to different time settings, with higher schedule numbers indicating slower system evolution. Consequently, this leads to a higher probability of obtaining favorable results. Additionally, there are two different device settings that can modify each :code:`relaxation schedule`:

* **mean_photon_number**: Sets the average number of photons present in a given quantum state. Allowed range: [0.0000667, 0.0066666].
* **quantum_fluctuation_coefficient**: specifies the number of photons (:math:`N`) for each feedback loop which controls the shot noise. The number of photons is inversely related (e.g. increasing number of photons decreases noise; fewer photons increases noise) to the shot noise with the following equation:

    .. math::
       \delta x \propto \frac{1}{\sqrt{N}}

 Allows integer values from [0,100].

If not specified, the system defaults for each schedule will be applied. These parameters are still an active area of research, so manipulation from standard schedules is considered experimental. Below is an example of a parameter sweep taken for LABS 10:

.. figure:: _static/MPN_QFC.png
   :alt: prop_optimal_labs_10
   :width: 80%
   :align: center

   A sweep of values for :code:`mean_photon_number` and :code:`quantum fluctuation coefficient` on problem `QPLIB0018 <https://qplib.zib.de/QPLIB_0018>`_ from the library of quadratic programming instances (QPLIB) (Furini et al, 2018) 
   

An example using relaxation schedule with tuned parameters
-------------------------------------------------------------

Find the minimium of: 

.. math::
    f(X) = (x_1 - 1)^2 + (x_2 - 1.375)^2
    
where :math:`x_1` and :math:`x_2` are both in :math:`\{0, 0.5, 1, 1.5, 2, \ldots\}`,
i.e, minimizing over a non-integral lattice in the positive quadrant.

By inspection, we can see that, without any domain restriction, :math:`f(X)` has a unique
global minimum of zero at :math:`x_1 = 1` and :math:`x_2 = 1.375`. However, that
solution is not on the lattice domain, so one expects the optimal feasible solution to
be :math:`x_1 = 1` and :math:`x_2 = 1.5` with :math:`f(X) = 0.015625`.

We now verify this using using Dirac-3. First, expand :math:`f(X)` as a polynomial:

.. math::
    f(X) = x_1^2 - 2 x_1 + 1^2 + x_2^2 - 2.75 x_2 + 1.375^2.

Dropping the constant terms, which do not affect the minimization problem and are not
supported by Dirac-3, gives:

.. math::
    f(X)' = x_1^2 - 2 x_1 + x_2^2 - 2.75 x_2.

This minimization problem does not appear to be particularly hard, so we will start with
``relaxation_schedule=1``. We need to choose a ``sum_constraint``, ``num_variables``,
and ``solution_precision`` to search a sufficiently large portion of the domain lattice
spaced at 0.5 in each variable. This can be difficult in larger dimensional problems
where one has less insight as to where the optimal solution may lie. Furthermore, the
sum constraint means that the domain searched forms a non-rectangular 
`simplex <https://en.wikipedia.org/wiki/Simplex>`_. To this end, we take
``num_variables=3``, ``sum_constraint=3``, and ``solution_precision=0.5``, so that the
constrained domain is large enough to encompass the region where we suspect the optimal
solution to lie. Lastly, we set ``num_samples=5`` to examine repeatability.

.. code-block:: python

    from pprint import pprint

    import numpy np

    from eqc_direct.client import EqcClient

    # MUST FILL IN THE VALUES IN THIS FROM YOUR NETWORKING SETUP FOR THE DEVICE
    client = EqcClient(ip_address="<YOUR DEVICE IP ADDRESS>", port="<YOUR DEVICE PORT>")
    poly_indices = np.array([[0, 1], [0, 2], [1, 1], [2, 2]], dtype=np.uint32)
    poly_coefficients = np.array([-2.0, -2.75, 1.0, 1.0], dtype=np.float32)
    # use lock to prevent other users from taking exclusive access to the device
    lock_id, start_ts, end_ts = client.wait_for_lock()
    print(f"Waited for lock: {(end_ts - start_ts) / 1e9} s for lock_id={lock_id}.")
    try:
	result = client.solve_sum_constrained(
	    lock_id=lock_id,
	    poly_indices=poly_indices,
	    poly_coefficients=poly_coefficients,
	    num_variables=3,
	    sum_constraint=3,
	    solution_precision=0.5,
	    mean_photon_number=0.00333,
	    quantum_fluctuation_coefficient=4,
	    num_samples=5,
	)
	print("Total execution time (s):",
	      np.array(result["preprocessing_time"])+np.array(result["postprocessing_time"])+np.array(result["runtime"]))
	pprint(result)
    finally:
	# release lock when finished using the device
	lock_release_out = client.release_lock(lock_id=lock_id)
	print(f"Lock release returned {lock_release_out}")


.. Using TLS to encrypt communication with hardware
.. --------------------------------------------------
   
    In order to allow users to keep their data private and secure there is an option to
    enable TLS encryption and authentication with the device. By default this feature
    is disabled In order to enable encryption see section
    *Remote Command Line Interface for Administrators*
    in `Dirac-3 User Guide <https://quantumcomputinginc.com/learn/module/introduction-to-dirac-3/dirac-3-users-guide>`_.
    After following directions there in each user will be provided with a certificate pem
    file which will be used to authenticate with the hardware. If TLS has been enabled
    for the device then all communication with the device must be conducted using the
    same certificate pem file using TLS. In order to enable TLS in the client simply
    initialize the client as follows:

    .. .. code-block:: python

	from eqc_direct.client import EqcClient

	# MUST FILL IN THE VALUES FROM YOUR NETWORKING SETUP FOR DEVICE
	# ALSO MUST ADD PATH TO YOUR CERTIFICATE PEM FILE AS WELL

	eqc_client = EqcClient(
	    ip_addr="YOUR DEVICE IP ADDRESS",
	    port="YOUR DEVICE PORT",
	    cert_file="LOCAL PATH TO YOUR CERT",
	)

    After this initialization, all other functions are identical but are run with the added security of authentication and encryption of the communication using TLS.

Additional Resources
----------------------

To learn more about the theory and function of the device please use the followign resources:

- `Dirac-3 User Guide <https://quantumcomputinginc.com/learn/support/user-guides/dirac-3-user-guide>`_
- `Entropy Computing: A Paradigm for Optimization in an Open Quantum System <https://arxiv.org/pdf/2407.04512>`_ (Nguyen et al, 2025)
    
Troubleshooting and Support
-----------------------------

If you encounter any issues or errors that are not covered in this manual or the user guide for the device and require additional assistance contact our customer support
at `https://quantumcomputinginc.ladesk.com <https://quantumcomputinginc.ladesk.com/>`_.
