Managing Optimizations and Solutions

Luna simplifies the process of creating, storing, and managing optimizations, allowing you to efficiently solve complex problems. By leveraging Luna's intuitive interface and powerful tools, you can seamlessly define optimization problems, execute them on classical, quantum, or hybrid hardware, and analyze the results. Additionally, Luna provides robust mechanisms for managing solutions, enabling you to store, visualize and refine them to achieve optimal outcomes. This streamlined approach enhances your workflow and ensures that you can focus on finding the best solutions for your problems.

Upon completing this tutorial, you will be able to:

  1. Define and Configure Optimization Problems in Luna.
  2. Effortlessly execute and manage solutions using a wide array of algorithms.

Managing Optimizations

First, you'll need to upload an optimization problem to Luna. In this example, we'll use a simple optimization in the BQM format.

from luna_sdk.schemas.optimization_formats.bqm import BQMSchema

# Define your QUBO problem as a BQM
bqm_data = {
    "linear": {"0": 4.0, "1": -2.0, "2": 6.0, "3": 2.0, "4": 5.0},
    "quadratic": {("3", "2"): -6.0, ("4", "0"): -4.0},
    "vartype": "BINARY"
}

bqm = BQMSchema(**bqm_data)

# Upload your BQM to LunaSolve
optimization = ls.optimization.create_from_bqm(name="My BQM", bqm=bqm)

Luna makes it easy to manage all your stored optimizations. You can retrieve, edit, and delete them.

# Retrieve an optimization
optimization = ls.optimization.get(optimization_id=optimization.id)

print(optimization)

Output:

quadratic:
    ('3', '2'): -6.0
    ('4', '0'): -4.0
linear:
    2: 6.0
    3: 2.0
    0: 4.0
    4: 5.0
    1: -2.0
vartype: BQMVarType.BINARY
offset: 0.0
id: 664e1452acaf90d0a917a4e3
name: My BQM
created_date: 2024-05-22 17:50:42.070000
created_by: 66291cab401df553a5f5ccd6
modified_date: None
modified_by: None
use_case_name: None
params: None

Furthermore, you can retrieve ally our created optimization problems:

from luna_sdk.schemas.enums.timeframe import TimeframeEnum

all_optimizations = luna.optimization.get_all(
    timeframe=TimeframeEnum.this_week, #Timeframe when solution was created
    input_type=OptFormat.qubo, # Format of the optimization problem
    limit=10  #Limit of optimizations returned, default is 50.
    offset=0, #Pagination
)
# Rename an optimization
updated_optimization = ls.optimization.rename(optimization_id=optimization.id, name="My Updated BQM")

print(updated_optimization)

Output:

id: 664e1452acaf90d0a917a4e3
name: My Updated BQM
created_date: 2024-05-22 17:50:42.070000
created_by: 66291cab401df553a5f5ccd6
modified_date: None
modified_by: 66291cab401df553a5f5ccd6
use_case_name: None
params: None
# Delete an optimization
ls.optimization.delete(optimization_id=optimization.id)

Managing Solutions

First, you'll need to create a solution for an optimization via Luna. We'll use the same BQM from earlier and generate a simple solution using the Simulated Annealing algorithm.

# Define your QUBO problem as a BQM
bqm_data = {
    "linear": {"0": 4.0, "1": -2.0, "2": 6.0, "3": 2.0, "4": 5.0},
    "quadratic": {("3", "2"): -6.0, ("4", "0"): -4.0},
    "vartype": "BINARY"
}

bqm = BQMSchema(**bqm_data)

# Upload your BQM to LunaSolve
optimization = ls.optimization.create_from_bqm(name="My BQM", bqm=bqm)

# Create a solution for the BQM using SA
job = ls.solution.create(
    optimization_id=optimization.id,
    solver_name="SA",
    provider="dwave"
)

Similar to optimizations, Luna provides the same features for managing solutions. Users can easily print solution details, obtaining all necessary information for their problem. Luna provides two options for solution visualization. Using print(solution) displays all problem solutions, print(solution.head) highlights 5 solutions among all the availabe possibilities. If you only need to verify that the solution process was successful and don't require all solutions to be displayed, you can use print(solution.head). This will provide the necessary information about the solution object without displaying every single solution.

# Retrieve an optimization
solution = ls.solution.get(solution_id=job.id)

print(solution.head)

Output:

--------------------------------------------------------------------------------
META DATA:
--------------------------------------------------------------------------------
id: 664e1574ca894b28dc87b1e0
name: Your_Solution_Name
created_date: 2024-05-22 17:55:32.998000
created_by: 66291cab401df553a5f5ccd6
modified_date: 2024-05-22 17:55:33.116000
modified_by: None
error_message: None
params:
    num_reads: None
    num_sweeps: 1000
    beta_range: None
    beta_schedule_type: geometric
    initial_states_generator: random
    num_sweeps_per_beta: 1
    seed: None
    interrupt_function: None
    beta_schedule: None
    initial_states: None
    randomize_order: False
    proposal_acceptance_criteria: Metropolis
runtime:
    total: 0.005857708998519229
    qpu: None
sense: SenseEnum.MIN
solver: SA
provider: dwave
status: StatusEnum.DONE
optimization:
    id: 664e1574ca894b28dc87b1df
    name: My BQM
    created_date: 2024-05-22 17:55:32.366000
    created_by: 66291cab401df553a5f5ccd6
    modified_date: None
    modified_by: None
    use_case_name: None
    params: None
representation: None


--------------------------------------------------------------------------------
RESULTS:
--------------------------------------------------------------------------------
1 results found. Displaying first 5 results.
Result 1:
    {'sample': {'0': 0.0, '1': 1.0, '2': 0.0, '3': 0.0, '4': 0.0}, 'obj_value': -2.0, 'feasible': True, 'constraints': {}}


--------------------------------------------------------------------------------
DWAVE META DATA:
--------------------------------------------------------------------------------
beta_range: [0.11552453009332421, 4.605170185988092]
beta_schedule_type: geometric
timing:
    preprocessing_ns: 5021833
    sampling_ns: 638458
    postprocessing_ns: 189417

# Retrieve all solutions available to you
all_solutions = ls.solution.get_all(
    timeframe=TimeframeEnum.this_week, #Timeframe when solution was created
    limit=10  # Limit of solutions returned, default is 50.
    offset=0, #Pagination
)
# Delete a solution
ls.solution.delete(solution_id=job.id)

Because some algorithms return multiple possible solutions, Luna allows you to retrieve only the best result from any created solutions.

ls.solution.get_best_result(solution)

When you create a solution through our use case library, Luna also offers the option to directly access the best result within the use case representation.

import networkx as nx
from luna_sdk.schemas import TravellingSalesmanProblem

# Create an empty graph using networkx
graph = nx.Graph()

# Define the cities we want to visit
cities = ['Berlin', 'Hamburg', 'Munich']

# Add the cities as nodes in our graph
graph.add_nodes_from(cities)

# Define the distances between each city
edges = [('Berlin', 'Hamburg', 289),
         ('Berlin', 'Munich', 586),
         ('Hamburg', 'Munich', 796)]

# Add the distances as the edges in our graph
graph.add_weighted_edges_from(edges)

# Convert the networkx graph to a digestible format for Luna
graph_data = nx.to_dict_of_dicts(graph)

# Define that we want to create a TSP with the corresponding graph data
tsp = TravellingSalesmanProblem(graph=graph_data)

# Create a TSP instance and retrieve the corresponding ID
optimization = ls.optimization.create_from_use_case(name="My TSP", use_case=tsp)

# Create a solution for the TSP using SA
job = ls.solution.create(
    optimization_id=optimization.id,
    solver_name="SA",
    provider="dwave"
)

# Instead of retrieving the raw solution, get it in the format fitting for your problem formulation
use_case_solution = ls.solution.get_use_case_representation(job.id)

# Finally, get the best result directly in the use case representation
result = ls.solution.get_best_use_case_result(use_case_solution)

print(result)

Output:

representation=[['Hamburg'], ['Munich'], ['Berlin']] obj_value=-3111.0

Interpreting Solution Runtimes

Each solution you create via Luna contains a Runtime object, which itself has the fields total and qpu. The total field holds a value we measured ourselves: it is the time in seconds that elapsed from between calling a solver and getting back a result from the solver. The qpu field only holds a value when the solver that created the solution runs on a backend other than our own servers. It reports the runtime in seconds on the hardware of our providers.

For example, if you had created the solution in the previous section with D-Wave's quantum annealer, you could expect the solution runtime to be of the following format:

print(solution.runtime)

Output:

total=3.362961308001104 qpu=0.01587886

Depending on the provider and the specific solver, solutions may also have timing information in the solution's metadata field. These metadata differ from provider to provider, and we mostly just pass on the values we obtain from the provider. The expected timing metadata per provider are summarized below.

D-Wave

For a general overview over which runtime metadata to expect from D-Wave solvers, check out D-Wave's website.

When you create a solution with D-Wave's quantum annealer, the metadata may look like this:

print(solution)

Output:

<...>

--------------------------------------------------------------------------------
DWAVE META DATA:
--------------------------------------------------------------------------------
timing:
    qpu_sampling_time: 94.5
    qpu_anneal_time_per_sample: 20
    qpu_readout_time_per_sample: 53.92
    qpu_access_time: 15878.86
    qpu_access_overhead_time: 1.14
    qpu_programming_time: 15784.36
    qpu_delay_time_per_sample: 20.58
    total_post_processing_time: 1
    post_processing_overhead_time: 1
problem_id: "75464a4b-64aa-495a-bdd6-b08344b1e9f8"

Was this page helpful?