[Python] Calling a Python function inside a Z3 objective function

Discussão em 'Python' iniciado por Stack, Março 26, 2025.

  1. Stack

    Stack Membro Participativo

    The issue I am facing is that I cannot convert the Z3 array to a python integer list. I want to call a python function inside Z3 objective function and the python function takes an integer list. I am having a Z3 array but the code gives me an error when I am converting it to a list. Below is the error message I am getting:

    Traceback (most recent call last):
    File "C:\Users\sarda\Desktop\Dr. Kovalenkov\Python Coding\cybersecurity\inspection_state_placement.py", line 132, in <module>
    solver.add(D == get_paths_and_sum(connectivity, distance, valid_inspections)[1])
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "C:\Users\sarda\Desktop\Dr. Kovalenkov\Python Coding\cybersecurity\inspection_state_placement.py", line 120, in get_paths_and_sum
    result = max_path_with_path(connectivity, distance, inspections, i, j)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "C:\Users\sarda\Desktop\Dr. Kovalenkov\Python Coding\cybersecurity\inspection_state_placement.py", line 100, in max_path_with_path
    return dfs(start, target, frozenset({start}))
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "C:\Users\sarda\Desktop\Dr. Kovalenkov\Python Coding\cybersecurity\inspection_state_placement.py", line 82, in dfs
    if connectivity[current, neighbor] == 1:
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
    IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices


    I intend to have the optimizer found the optimal set of x for which selection_cost and get_paths_and_sum. I don't know how to describe the get_paths_and_sum using Z3 idioms. Can someone help me fix the code? Is there a way I don't change my python code and convert the array before it is being used by the function?

    from z3 import *
    import numpy as np
    from functools import lru_cache

    Q = 11 # Number of states
    connectivity = np.zeros((Q, Q), dtype=int)


    # Helper to add edges
    def add_edge(u, v):
    connectivity[u, v] = 1


    n = 5 # Number of inspection states
    initial_state = 0 # Ensure this is an inspection state
    last_state = 4 # Ensure this is an inspection state

    # Cost of placing inspection states
    costs = [1 for i in range(Q)] # Adjust as needed

    # Define edges between states
    add_edge(0, 1)
    add_edge(0, 5)
    add_edge(0, 8)
    add_edge(1, 2)
    add_edge(2, 3)
    add_edge(3, 4)
    add_edge(5, 2)
    add_edge(5, 6)
    add_edge(6, 3)
    add_edge(6, 7)
    add_edge(7, 4)
    add_edge(8, 9)
    add_edge(9, 5)
    add_edge(9, 7)
    add_edge(9, 10)
    add_edge(10, 4)

    # Distance = 1 per edge
    distance = connectivity.astype(float)

    # Solver setup
    solver = Optimize()

    # Decision variables: x = True if state i is an inspection state
    x = [Bool(f"x_{i}") for i in range(Q)]

    # Ensure exactly n states are selected
    solver.add(Sum([If(x, 1, 0) for i in range(Q)]) == n)

    # Force the initial and last states to be inspection states
    solver.add(x[initial_state] == True)
    solver.add(x[last_state] == True)

    # Cost function: sum of selected inspection state costs
    selection_cost = Sum([If(x, costs, 0) for i in range(Q)])

    # Define a Z3 array valid_inspections
    valid_inspections = Array('valid_inspections', IntSort(), IntSort())

    # Constraint: If x is true, set valid_inspections = i
    for i in range(Q):
    solver.add(If(x, valid_inspections == i, valid_inspections == -1))


    # Compute sum of the longest paths between neighboring inspection states
    def max_path_with_path(connectivity, distance, inspections, start, target):
    """
    Returns (max_length, path) for the maximum-length path from 'start' to 'target'
    that does NOT pass through any inspection state except at the endpoints.
    If no valid path exists, returns None.
    """
    n = connectivity.shape[0]

    @lru_cache(None)
    def dfs(current, target, visited):
    if current == target:
    return (0, (current,)) # Reached target -> path length = 0 from here, path is just (target,)

    best = None # Will hold (length, path) for the best route found
    for neighbor in range(n):
    if connectivity[current, neighbor] == 1:
    # Avoid cycles
    if neighbor in visited:
    continue
    # Skip if neighbor is an inspection state (and not the target)
    if neighbor in inspections and neighbor != target:
    continue

    result = dfs(neighbor, target, visited | {neighbor})
    if result is not None:
    sub_len, sub_path = result
    total_len = distance[current, neighbor] + sub_len
    candidate_path = (current,) + sub_path
    if best is None or total_len > best[0]:
    best = (total_len, candidate_path)

    return best

    return dfs(start, target, frozenset({start}))


    def get_paths_and_sum(connectivity, distance, inspections):
    """
    For every ordered pair (i, j) of distinct inspection states, find the maximum-length path
    from i to j that doesn't pass through any OTHER inspection state.

    Returns:
    paths_dict: dict[(i,j)] = (path, length)
    total_sum: sum of all such maximum path lengths
    """
    # Convert valid_inspections Z3 array to a list of integers

    paths_dict = {}
    total_sum = 0
    for i in inspections:
    for j in inspections:
    if i == j:
    continue
    result = max_path_with_path(connectivity, distance, inspections, i, j)
    if result is not None:
    length, path = result
    paths_dict[(i, j)] = (path, length)
    total_sum += length
    return paths_dict, total_sum


    # Define an integer variable for path sum (D)
    D = Int("D")

    # Add the constraint that D is equal to the total sum of paths
    solver.add(D == get_paths_and_sum(connectivity, distance, valid_inspections)[1])

    # Add the constraint to ensure D is non-negative
    solver.add(D >= 0)

    # Objective function: minimize selection cost + weighted sum of longest paths
    lambda_penalty = 1
    solver.minimize(selection_cost + lambda_penalty * D)

    # Solve the problem
    if solver.check() == sat:
    model = solver.model()

    # Extract selected inspection states
    selected_states = [i for i in range(Q) if model.evaluate(x)]

    # Calculate total cost
    paths_dict_f, max_distance = get_paths_and_sum(connectivity, distance, valid_inspections)

    total_cost = model.evaluate(selection_cost).as_long() + lambda_penalty * max_distance

    print(f"Selected inspection states: {selected_states}")
    print(f"Sum of longest paths between neighboring inspection states: {max_distance}")
    print(f"Total cost: {total_cost}")
    else:
    print("No solution found.")

    Continue reading...

Compartilhe esta Página