<problem-description>
You are a Python programmer creating a domain-dependent heuristic function for the PDDL domain <domain>Logistics</domain>. You will receive the PDDL domain file, two example PDDL instance files, two Python files with example domain-independent heuristic functions, an example of a set of facts from the domain and how they are represented internally, one example of a set of static facts and how they are represented internally, and one file with Python code for representing a planning task. The domain-dependent heuristic function you create should take into consideration the current state and the goal condition of the instance, and the actions available in the PDDL domain file. PDDL facts are represented as strings (e.g., '(pred obj1 ob2)'). The heuristic function you create will be used to solve other instances of the same domain. The heuristic function should be efficiently computable and accurate, that is, it should minimize the number of expanded nodes during the search. The name of the heuristic should be $heuristic_name.
</problem-description>

<domain-file>
(define (domain logistics-strips)
  (:requirements :strips)
  (:predicates 	(OBJ ?obj)
	       	(TRUCK ?truck)
               	(LOCATION ?loc)
		(AIRPLANE ?airplane)
                (CITY ?city)
                (AIRPORT ?airport)
		(at ?obj ?loc)
		(in ?obj1 ?obj2)
		(in-city ?obj ?city))

  ; (:types )		; default object

(:action LOAD-TRUCK
  :parameters
   (?obj
    ?truck
    ?loc)
  :precondition
   (and (OBJ ?obj) (TRUCK ?truck) (LOCATION ?loc)
   (at ?truck ?loc) (at ?obj ?loc))
  :effect
   (and (not (at ?obj ?loc)) (in ?obj ?truck)))

(:action LOAD-AIRPLANE
  :parameters
   (?obj
    ?airplane
    ?loc)
  :precondition
   (and (OBJ ?obj) (AIRPLANE ?airplane) (LOCATION ?loc)
   (at ?obj ?loc) (at ?airplane ?loc))
  :effect
   (and (not (at ?obj ?loc)) (in ?obj ?airplane)))

(:action UNLOAD-TRUCK
  :parameters
   (?obj
    ?truck
    ?loc)
  :precondition
   (and (OBJ ?obj) (TRUCK ?truck) (LOCATION ?loc)
        (at ?truck ?loc) (in ?obj ?truck))
  :effect
   (and (not (in ?obj ?truck)) (at ?obj ?loc)))

(:action UNLOAD-AIRPLANE
  :parameters
   (?obj
    ?airplane
    ?loc)
  :precondition
   (and (OBJ ?obj) (AIRPLANE ?airplane) (LOCATION ?loc)
        (in ?obj ?airplane) (at ?airplane ?loc))
  :effect
   (and (not (in ?obj ?airplane)) (at ?obj ?loc)))

(:action DRIVE-TRUCK
  :parameters
   (?truck
    ?loc-from
    ?loc-to
    ?city)
  :precondition
   (and (TRUCK ?truck) (LOCATION ?loc-from) (LOCATION ?loc-to) (CITY ?city)
   (at ?truck ?loc-from)
   (in-city ?loc-from ?city)
   (in-city ?loc-to ?city))
  :effect
   (and (not (at ?truck ?loc-from)) (at ?truck ?loc-to)))

(:action FLY-AIRPLANE
  :parameters
   (?airplane
    ?loc-from
    ?loc-to)
  :precondition
   (and (AIRPLANE ?airplane) (AIRPORT ?loc-from) (AIRPORT ?loc-to)
	(at ?airplane ?loc-from))
  :effect
   (and (not (at ?airplane ?loc-from)) (at ?airplane ?loc-to)))
)
</domain-file>

<instance-file-example-1>
(define (problem strips-log-x-2)
   (:domain logistics-strips)
   (:objects package5 package4 package3 package2 package1 city10
             city9 city8 city7 city6 city5 city4 city3 city2 city1 truck10
             truck9 truck8 truck7 truck6 truck5 truck4 truck3 truck2 truck1
             plane4 plane3 plane2 plane1 city10-1 city9-1 city8-1 city7-1
             city6-1 city5-1 city4-1 city3-1 city2-1 city1-1 city10-2
             city9-2 city8-2 city7-2 city6-2 city5-2 city4-2 city3-2
             city2-2 city1-2)
   (:init (obj package5)
          (obj package4)
          (obj package3)
          (obj package2)
          (obj package1)
          (city city10)
          (city city9)
          (city city8)
          (city city7)
          (city city6)
          (city city5)
          (city city4)
          (city city3)
          (city city2)
          (city city1)
          (truck truck10)
          (truck truck9)
          (truck truck8)
          (truck truck7)
          (truck truck6)
          (truck truck5)
          (truck truck4)
          (truck truck3)
          (truck truck2)
          (truck truck1)
          (airplane plane4)
          (airplane plane3)
          (airplane plane2)
          (airplane plane1)
          (location city10-1)
          (location city9-1)
          (location city8-1)
          (location city7-1)
          (location city6-1)
          (location city5-1)
          (location city4-1)
          (location city3-1)
          (location city2-1)
          (location city1-1)
          (airport city10-2)
          (location city10-2)
          (airport city9-2)
          (location city9-2)
          (airport city8-2)
          (location city8-2)
          (airport city7-2)
          (location city7-2)
          (airport city6-2)
          (location city6-2)
          (airport city5-2)
          (location city5-2)
          (airport city4-2)
          (location city4-2)
          (airport city3-2)
          (location city3-2)
          (airport city2-2)
          (location city2-2)
          (airport city1-2)
          (location city1-2)
          (in-city city10-2 city10)
          (in-city city10-1 city10)
          (in-city city9-2 city9)
          (in-city city9-1 city9)
          (in-city city8-2 city8)
          (in-city city8-1 city8)
          (in-city city7-2 city7)
          (in-city city7-1 city7)
          (in-city city6-2 city6)
          (in-city city6-1 city6)
          (in-city city5-2 city5)
          (in-city city5-1 city5)
          (in-city city4-2 city4)
          (in-city city4-1 city4)
          (in-city city3-2 city3)
          (in-city city3-1 city3)
          (in-city city2-2 city2)
          (in-city city2-1 city2)
          (in-city city1-2 city1)
          (in-city city1-1 city1)
          (at plane4 city3-2)
          (at plane3 city7-2)
          (at plane2 city3-2)
          (at plane1 city6-2)
          (at truck10 city10-1)
          (at truck9 city9-1)
          (at truck8 city8-1)
          (at truck7 city7-1)
          (at truck6 city6-1)
          (at truck5 city5-1)
          (at truck4 city4-1)
          (at truck3 city3-1)
          (at truck2 city2-1)
          (at truck1 city1-1)
          (at package5 city1-2)
          (at package4 city7-2)
          (at package3 city3-2)
          (at package2 city10-1)
          (at package1 city2-2))
   (:goal (and (at package5 city4-2)
               (at package4 city6-1)
               (at package3 city1-1)
               (at package2 city9-2)
               (at package1 city3-1))))
</instance-file-example-1>

<instance-file-example-2>
(define (problem strips-log-x-3)
   (:domain logistics-strips)
   (:objects package9 package8 package7 package6 package5 package4
             package3 package2 package1 city14 city13 city12 city11 city10
             city9 city8 city7 city6 city5 city4 city3 city2 city1 truck14
             truck13 truck12 truck11 truck10 truck9 truck8 truck7 truck6
             truck5 truck4 truck3 truck2 truck1 plane4 plane3 plane2 plane1
             city14-2 city14-1 city13-2 city13-1 city12-2 city12-1 city11-2
             city11-1 city10-2 city10-1 city9-2 city9-1 city8-2 city8-1
             city7-2 city7-1 city6-2 city6-1 city5-2 city5-1 city4-2
             city4-1 city3-2 city3-1 city2-2 city2-1 city1-2 city1-1
             city14-3 city13-3 city12-3 city11-3 city10-3 city9-3 city8-3
             city7-3 city6-3 city5-3 city4-3 city3-3 city2-3 city1-3)
   (:init (obj package9)
          (obj package8)
          (obj package7)
          (obj package6)
          (obj package5)
          (obj package4)
          (obj package3)
          (obj package2)
          (obj package1)
          (city city14)
          (city city13)
          (city city12)
          (city city11)
          (city city10)
          (city city9)
          (city city8)
          (city city7)
          (city city6)
          (city city5)
          (city city4)
          (city city3)
          (city city2)
          (city city1)
          (truck truck14)
          (truck truck13)
          (truck truck12)
          (truck truck11)
          (truck truck10)
          (truck truck9)
          (truck truck8)
          (truck truck7)
          (truck truck6)
          (truck truck5)
          (truck truck4)
          (truck truck3)
          (truck truck2)
          (truck truck1)
          (airplane plane4)
          (airplane plane3)
          (airplane plane2)
          (airplane plane1)
          (location city14-2)
          (location city14-1)
          (location city13-2)
          (location city13-1)
          (location city12-2)
          (location city12-1)
          (location city11-2)
          (location city11-1)
          (location city10-2)
          (location city10-1)
          (location city9-2)
          (location city9-1)
          (location city8-2)
          (location city8-1)
          (location city7-2)
          (location city7-1)
          (location city6-2)
          (location city6-1)
          (location city5-2)
          (location city5-1)
          (location city4-2)
          (location city4-1)
          (location city3-2)
          (location city3-1)
          (location city2-2)
          (location city2-1)
          (location city1-2)
          (location city1-1)
          (airport city14-3)
          (location city14-3)
          (airport city13-3)
          (location city13-3)
          (airport city12-3)
          (location city12-3)
          (airport city11-3)
          (location city11-3)
          (airport city10-3)
          (location city10-3)
          (airport city9-3)
          (location city9-3)
          (airport city8-3)
          (location city8-3)
          (airport city7-3)
          (location city7-3)
          (airport city6-3)
          (location city6-3)
          (airport city5-3)
          (location city5-3)
          (airport city4-3)
          (location city4-3)
          (airport city3-3)
          (location city3-3)
          (airport city2-3)
          (location city2-3)
          (airport city1-3)
          (location city1-3)
          (in-city city14-3 city14)
          (in-city city14-2 city14)
          (in-city city14-1 city14)
          (in-city city13-3 city13)
          (in-city city13-2 city13)
          (in-city city13-1 city13)
          (in-city city12-3 city12)
          (in-city city12-2 city12)
          (in-city city12-1 city12)
          (in-city city11-3 city11)
          (in-city city11-2 city11)
          (in-city city11-1 city11)
          (in-city city10-3 city10)
          (in-city city10-2 city10)
          (in-city city10-1 city10)
          (in-city city9-3 city9)
          (in-city city9-2 city9)
          (in-city city9-1 city9)
          (in-city city8-3 city8)
          (in-city city8-2 city8)
          (in-city city8-1 city8)
          (in-city city7-3 city7)
          (in-city city7-2 city7)
          (in-city city7-1 city7)
          (in-city city6-3 city6)
          (in-city city6-2 city6)
          (in-city city6-1 city6)
          (in-city city5-3 city5)
          (in-city city5-2 city5)
          (in-city city5-1 city5)
          (in-city city4-3 city4)
          (in-city city4-2 city4)
          (in-city city4-1 city4)
          (in-city city3-3 city3)
          (in-city city3-2 city3)
          (in-city city3-1 city3)
          (in-city city2-3 city2)
          (in-city city2-2 city2)
          (in-city city2-1 city2)
          (in-city city1-3 city1)
          (in-city city1-2 city1)
          (in-city city1-1 city1)
          (at plane4 city10-3)
          (at plane3 city1-3)
          (at plane2 city11-3)
          (at plane1 city11-3)
          (at truck14 city14-1)
          (at truck13 city13-1)
          (at truck12 city12-1)
          (at truck11 city11-1)
          (at truck10 city10-1)
          (at truck9 city9-2)
          (at truck8 city8-2)
          (at truck7 city7-1)
          (at truck6 city6-2)
          (at truck5 city5-1)
          (at truck4 city4-2)
          (at truck3 city3-2)
          (at truck2 city2-1)
          (at truck1 city1-1)
          (at package9 city4-2)
          (at package8 city9-1)
          (at package7 city1-2)
          (at package6 city9-1)
          (at package5 city12-2)
          (at package4 city8-1)
          (at package3 city12-3)
          (at package2 city12-2)
          (at package1 city8-3))
   (:goal (and (at package9 city11-2)
               (at package8 city5-3)
               (at package7 city10-3)
               (at package6 city9-3)
               (at package5 city10-1)
               (at package4 city13-1)
               (at package3 city1-3))))
</instance-file-example-2>

<code-file-heuristic-1>
from fnmatch import fnmatch
from heuristics.heuristic_base import Heuristic

class GripperHeuristic(Heuristic):
    """
    A domain-dependent heuristic for the Gripper domain.

    This heuristic estimates the number of actions needed to transport all balls
    from `rooma` to `roomb`, considering:

    - The robot has two grippers, allowing it to carry up to two balls per trip.
    - The robot must return to rooma after each trip, except for the final trip.
    - If the robot starts in roomb, it must move to rooma first.

    # Heuristic Cost Calculation
    The number of required trips is:
    `ceil(balls_in_rooma / 2)`, since each trip moves at most 2 balls.

    Each trip consists of:
    1. Picking up 2 balls (`2` pick actions).
    2. Moving to Room B (`1` move action).
    3. Dropping 2 balls (`2` drop actions).
    4. Moving back to Room A, unless it's the final trip (`1` move action).

    # Formula for Total Cost
    - Each full two-ball trip costs 6 actions (unless it's the final trip).
    - If the robot starts in roomb, an extra move action is required.

    # Edge Cases Handled
    - If the robot is already carrying balls, it accounts for these before computing trips.
    - If there is an odd number of balls, the last trip carries only one ball.
    - If the robot carries one ball while an odd number remains, it efficiently picks up another and moves.
    - If the robot starts in `roomb`, it drops any carried balls first.
    """

    def __init__(self, task):
        """Initialize the heuristic by extracting static domain facts and goal conditions."""
        self.goals = task.goals  # The set of facts that must hold in goal states.
        self.static_facts = task.static  # Static facts.

    def __call__(self, node):
        """Estimate the minimum cost to transport all remaining balls from room A to room B."""
        state = node.state

        def match(fact, *args):
            """
            Utility function to check if a PDDL fact matches a given pattern.
            - `fact`: The fact as a string (e.g., "(at ball1 rooma)").
            - `args`: The pattern to match (e.g., "at", "*", "rooma").
            - Returns `True` if the fact matches the pattern, `False` otherwise.
            """
            parts = fact[1:-1].split()  # Remove parentheses and split into individual elements.
            return all(fnmatch(part, arg) for part, arg in zip(parts, args))

        # Count how many balls are currently in room A.
        balls_in_room_a = sum(1 for fact in state if match(fact, "at", "*", "rooma"))

        # Count the number of balls currently held by the robot.
        balls_in_grippers = sum(1 for fact in state if match(fact, "carry", "*", "*"))

        # Check if the robot is in room A.
        robot_in_room_a = "(at-robby rooma)" in state

        # Define the cost of each individual action for readability.
        move_cost = 1  # Moving between rooms.
        pick_cost = 1  # Picking up a ball.
        drop_cost = 1  # Dropping a ball.

        total_cost = 0  # Initialize the heuristic cost.

        # Handle cases where the robot is already carrying balls.
        if robot_in_room_a:
            if balls_in_grippers == 2:
                # Both grippers are full, so move to room B and drop both balls.
                total_cost += move_cost + 2 * drop_cost
            elif balls_in_grippers == 1 and balls_in_room_a % 2 == 1:
                # Pick one more ball to fill both grippers, then move and drop both.
                total_cost += pick_cost + move_cost + 2 * drop_cost
                balls_in_room_a -= 1  # Since we moved one extra ball.
            elif balls_in_grippers == 1 and balls_in_room_a % 2 == 0:
                # Move with one ball and drop it, leaving an even number of balls.
                total_cost += move_cost + drop_cost
        else:
            # If the robot is in room B, it must drop any carried balls.
            total_cost += balls_in_grippers * drop_cost

        if balls_in_room_a > 0:
            # Move back to room A to continue transporting balls.
            total_cost += move_cost

            # Compute the number of trips with two balls.
            num_two_ball_trips = balls_in_room_a // 2

            # Each trip includes: 2 picks, 1 move to B, 2 drops and 1 move back to A (except for the last trip).
            total_cost += num_two_ball_trips * (2 * pick_cost + move_cost + 2 * drop_cost + move_cost) - 1

            # If there's a single ball left after the two-ball trips, go back to A and move the ball by itself.
            if balls_in_room_a % 2 == 1:
                total_cost += move_cost + pick_cost + move_cost + drop_cost

        # Return the estimated cost to goal state.
        return total_cost

</code-file-heuristic-1>

<facts>
frozenset({'(at plane2 city4-2)', '(at truck1 city1-1)', '(at package2 city1-2)', '(at truck5 city5-1)', '(at package5 city4-2)', '(at truck2 city2-1)', '(at plane1 city4-2)', '(at package6 city3-1)', '(at package3 city1-1)', '(at truck3 city3-1)', '(at truck6 city6-1)', '(at package1 city2-1)', '(at package4 city1-1)', '(at truck4 city4-1)'})
</facts>

<static>
frozenset({'(obj package4)', '(in-city city6-1 city6)', '(airport city3-2)', '(location city3-1)', '(city city3)', '(truck truck4)', '(obj package2)', '(location city5-2)', '(truck truck6)', '(city city5)', '(location city1-1)', '(in-city city1-2 city1)', '(location city2-2)', '(in-city city1-1 city1)', '(obj package3)', '(truck truck3)', '(city city2)', '(location city6-2)', '(location city4-2)', '(location city3-2)', '(in-city city3-2 city3)', '(airport city4-2)', '(in-city city6-2 city6)', '(city city6)', '(obj package5)', '(city city1)', '(location city4-1)', '(in-city city5-2 city5)', '(airport city1-2)', '(in-city city5-1 city5)', '(in-city city2-2 city2)', '(obj package6)', '(truck truck5)', '(location city2-1)', '(in-city city4-1 city4)', '(location city1-2)', '(truck truck2)', '(location city6-1)', '(airport city2-2)', '(in-city city3-1 city3)', '(obj package1)', '(truck truck1)', '(airplane plane2)', '(airport city6-2)', '(in-city city2-1 city2)', '(airport city5-2)', '(location city5-1)', '(in-city city4-2 city4)', '(airplane plane1)', '(city city4)'})
</static>

<code-file-task>
"""
Classes for representing a STRIPS planning task
"""


class Operator:
    """
    The preconditions represent the facts that have to be true
    before the operator can be applied.
    add_effects are the facts that the operator makes true.
    delete_effects are the facts that the operator makes false.
    """

    def __init__(self, name, preconditions, add_effects, del_effects):
        self.name = name
        self.preconditions = frozenset(preconditions)
        self.add_effects = frozenset(add_effects)
        self.del_effects = frozenset(del_effects)

    def applicable(self, state):
        """
        Operators are applicable when their set of preconditions is a subset
        of the facts that are true in "state".

        @return True if the operator's preconditions is a subset of the state,
                False otherwise
        """
        return self.preconditions <= state

    def apply(self, state):
        """
        Applying an operator means removing the facts that are made false
        by the operator from the set of true facts in state and adding
        the facts made true.

        Note that therefore it is possible to have operands that make a
        fact both false and true. This results in the fact being true
        at the end.

        @param state The state that the operator should be applied to
        @return A new state (set of facts) after the application of the
                operator
        """
        assert self.applicable(state)
        assert type(state) in (frozenset, set)
        return (state - self.del_effects) | self.add_effects

    def __eq__(self, other):
        return (
            self.name == other.name
            and self.preconditions == other.preconditions
            and self.add_effects == other.add_effects
            and self.del_effects == other.del_effects
        )

    def __hash__(self):
        return hash((self.name, self.preconditions, self.add_effects, self.del_effects))

    def __str__(self):
        s = "%s\n" % self.name
        for group, facts in [
            ("PRE", self.preconditions),
            ("ADD", self.add_effects),
            ("DEL", self.del_effects),
        ]:
            for fact in facts:
                s += "  {}: {}\n".format(group, fact)
        return s

    def __repr__(self):
        return "<Op %s>" % self.name


class Task:
    """
    A STRIPS planning task
    """

    def __init__(self, name, facts, initial_state, goals, operators, static):
        """
        @param name The task's name
        @param facts A set of all the fact names that are valid in the domain
        @param initial_state A set of fact names that are true at the beginning
        @param goals A set of fact names that must be true to solve the problem
        @param operators A set of operator instances for the domain
        @param static_info A set of facts that are true in every state
        """
        self.name = name
        self.facts = facts
        self.initial_state = initial_state
        self.goals = goals
        self.operators = operators
        self.static = static

    def goal_reached(self, state):
        """
        The goal has been reached if all facts that are true in "goals"
        are true in "state".

        @return True if all the goals are reached, False otherwise
        """
        return self.goals <= state

    def get_successor_states(self, state):
        """
        @return A list with (op, new_state) pairs where "op" is the applicable
        operator and "new_state" the state that results when "op" is applied
        in state "state".
        """
        return [(op, op.apply(state)) for op in self.operators if op.applicable(state)]

    def __str__(self):
        s = "Task {0}\n  Vars:  {1}\n  Init:  {2}\n  Goals: {3}\n  Ops:   {4}"
        return s.format(
            self.name,
            ", ".join(self.facts),
            self.initial_state,
            self.goals,
            "\n".join(map(repr, self.operators)),
        )

    def __repr__(self):
        string = "<Task {0}, vars: {1}, operators: {2}>"
        return string.format(self.name, len(self.facts), len(self.operators))

</code-file-task>

Provide only the Python code of the domain-dependent heuristic for the domain. Here is a checklist to help you with your code:
1) The code for extracting objects from facts remembers to ignore the surrounding brackets.
2) The heuristic is 0 only for goal states.
3) The heuristic value is finite for solvable states.
4) All used modules are imported.
5) Static facts are accessed correctly.


The simplest heuristic we consider is the single visit and
load/unload counting heuristic, h0. It includes estimates for
the number of vehicle movements and estimates for the num-
ber of applications of load and unload operators.
It is easy to see that if the position of a package is in the
same city as its destination location a package must be
• unloaded from a plane iff it is in a plane,
• loaded into a truck iff its position is not the destination
location and it is not in a truck, and unloaded from a truck iff its position is not the destination
location or it is in a truck.
Similarly, if the position of a package is not in the same
city as its destination a package must be
• loaded into a truck at its current position iff its position is
not an airport and it is not in a truck,
• unloaded from a truck at the airport of the current city iff
its position is not an airport or it is in a truck,
• loaded into a plane iff it is not in a plane,
• unloaded from a plane,
• loaded into a truck at the airport of the destination city iff
its destination location is not an airport, and
• unloaded from a truck in the destination city iff the desti-
nation location is not an airport.
The load/unload contributions to the heuristic estimate
are exact; in an optimal solution, packages should not be
reloaded between planes or between trucks in the same city.
