Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to validate that the preconditions of future actions still hold for a given current state #607

Open
3 tasks
scastro-bdai opened this issue Jun 5, 2024 · 4 comments

Comments

@scastro-bdai
Copy link

scastro-bdai commented Jun 5, 2024

User Story

As a user who wants to track the execution of plans to see if any preconditions have been violated, I want the ability to easily check the (grounded) preconditions and effects of my actual action instances.

Basically, the workflow would be to iteratively

  • Get a snapshot of the current state of the world
  • "Forward simulate" by applying the effects of the currently running action to get a hypothetical new state
  • Validate that this hypothetical state still meets the preconditions of my next action

Acceptance Criteria

  • Mechanism to get grounded preconditions and effects of action instances
  • Ability to apply effects of actions to a future simulated state
  • Ability to validate that a specific state meets the (grounded) preconditions of an action

Additional Material

I am not sure if I'm duplicating functionality because I can't read the code/documentation correctly, but this worked for me for the first criterion:

I was able to hack around this with the following code:

# Do the planning
with OneshotPlanner(name="tamer", problem_kind=problem.kind) as planner:
    result = planner.solve(problem)

# Try to parse out the preconditions and effects of an action
for action_instance in result.plan.actions:
    subs = {
        param: val for param, val in 
        zip(action_instance.action.parameters, action_instance.actual_parameters)
    }
    preconditions = [p.substitute(subs) for p in action_instance.action.preconditions]
    effects = [(e.fluent.substitute(subs), e.value) for e in action_instance.action.effects]
    print(f"Action instance: {action_instance}")
    print(f"  Preconditions: {preconditions}")
    print(f"  Effects: {effects}")

This code would yield something like this:

SequentialPlan:
    pick(robby, apple, kitchen)
    move(robby, kitchen, bedroom)
    place(robby, apple, bedroom)

Action instance: pick(robby, apple, kitchen)
  Preconditions: [((robot_at(robby, kitchen) and hand_empty(robby)) and at(apple, kitchen))]
  Effects: [(at(apple, kitchen), false), (hand_empty(robby), false), (holding(robby, apple), true), (can_move(robby), true)]

Action instance: move(robby, kitchen, bedroom)
  Preconditions: [robot_at(robby, kitchen), can_move(robby)]
  Effects: [(robot_at(robby, kitchen), false), (robot_at(robby, bedroom), true), (can_move(robby), false)]

Action instance: place(robby, apple, bedroom)
  Preconditions: [robot_at(robby, bedroom), holding(robby, apple)]
  Effects: [(at(apple, bedroom), true), (hand_empty(robby), true), (holding(robby, apple), false), (can_move(robby), true)]

I guess there are "Grounders" available in this library that appear to do the above, but they seem very heavyweight for what I'm trying to achieve.

Then, you could start with a specific state and apply the effects by doing:

new_state = problem.initial_values
new_state[some_effect] = True # or False

Finally, you could validate the preconditions by checking whether the new state includes them.

if new_state[some_precondition]:
    # Do something
    # Of course, need to also check for logical formulas on said preconditions

Am I on the right track? Would this be useful?

Attention Points

N/A


@scastro-bdai scastro-bdai changed the title Add ability to validate that the preconditions of future actions are valid for a given current state Add ability to validate that the preconditions of future actions for a given current state Jun 5, 2024
@scastro-bdai scastro-bdai changed the title Add ability to validate that the preconditions of future actions for a given current state Add ability to validate that the preconditions of future actions still hold for a given current state Jun 5, 2024
@Framba-Luca
Copy link
Contributor

Hi @scastro-bdai , check out the SequentialSimulator operation mode (documented here with also a notebook here).

It should fit your use-case as long as you work with SequentialPlans!

@scastro-bdai
Copy link
Author

scastro-bdai commented Jun 5, 2024

Thanks @Framba-Luca -- indeed that solves my use case!

with SequentialSimulator(problem) as sim:
    state = sim.get_initial_state()
    print(f"Initial sim state: {state}")

    act = result.plan.actions[0]
    res = sim.is_applicable(state, act)
    print(f"Is action {act} applicable? {res}")

    state._values[robot_at(robby, kitchen)] = Bool(False)
    res = sim.is_applicable(state, act)
    print(f"Is action {act} applicable? {res}")

yields

Initial sim state: {can_move(robby): true, hand_empty(robby): true, holding(robby, apple): false, holding(robby, banana): false, robot_at(robby, kitchen): true, robot_at(robby, bedroom): false, at(apple, kitchen): true, at(apple, bedroom): false, at(banana, kitchen): false, at(banana, bedroom): true}

Is action pick(robby, apple, kitchen) applicable? True

Is action pick(robby, apple, kitchen) applicable? False

I can also do:

sim.get_unsatisfied_conditions(state, act)

to get

([robot_at(robby, kitchen)], <InapplicabilityReasons.VIOLATES_CONDITIONS: 1>)

Maybe my one piece of feedback is to add a set_value() method to State so the line to manually mutate the state value doesn't index a private attribute _values.

@Framba-Luca
Copy link
Contributor

@scastro-bdai Actually the State should be immutable (eq and hash methods require immutability, otherwise bugs might arise; the internal implementations assumes immutability).

So, instead of changing a value, we designed the make_child method, that creates a new state with the updated values.

I am copy-pasting here the method because the UPState is not in the api documentation.

    def make_child(
        self,
        updated_values: Dict["up.model.FNode", "up.model.FNode"],
    ) -> "UPState":
        """
        Returns a different `UPState` in which every value in updated_values.keys() is evaluated as his mapping
        in new the `updated_values` dict and every other value is evaluated as in `self`.

        :param updated_values: The dictionary that contains the `values` that need to be updated in the new `UPState`.
        :return: The new `UPState` created.
        """

@scastro-bdai
Copy link
Author

Worked perfectly. Thanks for all your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants