Answered step by step
Verified Expert Solution
Link Copied!

Question

1 Approved Answer

The program is written in Python using Pycharm's notebook feature. I have posted where I am stuck on this work. Please review and see where

The program is written in Python using Pycharm's notebook feature. I have posted where I am stuck on this work. Please review and see where I am going wrong. Thank you.

you will be making a simple satisficing Minecraft crafting planner. Given an initial state, you will want to find a series of actions that gets you to a desired goal state. For fun, you will also use your implementations for Dijkstras and A* to show how they don't do well in a search space this large (without a lot of tweaking).

you will:

Parse and process a data file into a format that is usable in your code

make a representation of a state space such that you can check preconditions and apply effects in an efficient manner

make a method to convert the intuitive predicate based state system into a propositional system

Implement Width-Search utilizing Iterative Widening to efficiently find a plan that gets from an arbitrary state space to a desired state space

Compare Width-Search to optimal search methods such as Dijkstra's and A* to see how they compare in what they can efficiently solve

The goal of this assignment is for you to understand:

How to manipulate data (a boring, but CRUCIAL skill)

How to navigate a state space that doesn't map to standard geometry (i.e., domains outside of palt

How the pruning of a search space can result in a drastic reduction in the time spent performing the search

First we will load in crafting.json, a json file that

In [42]:

import json

from typing import NamedTuple, Dict, Tuple, Optional, Sequence, List, Set, FrozenSet

import array

import heapq

import time

import itertools

with open('Crafting.json') as f:

Crafting = json.load(f)

# List of items that can be in your inventory:

print(Crafting['Items'])

# example: ['bench', 'cart', ..., 'wood', 'wooden_axe', 'wooden_pickaxe']

# List of items needed to be in your inventory at the end of the plan:

# (okay to have more than this; some might be satisfied by initial inventory)

print(Crafting['Goal'])

# {'stone_pickaxe': 2}

# Dict of crafting recipes (each is a dict):

print(Crafting['Recipes']['craft stone_pickaxe at bench'])

# example:

# { 'Produces': {'stone_pickaxe': 1},

# 'Requires': {'bench': True},

# 'Consumes': {'cobble': 3, 'stick': 2},

# 'Time': 1

# }

['bench', 'cart', 'coal', 'cobble', 'furnace', 'ingot', 'iron_axe', 'iron_pickaxe', 'ore', 'plank', 'rail', 'stick', 'stone_axe', 'stone_pickaxe', 'wood', 'wooden_axe', 'wooden_pickaxe']

{'stone_pickaxe': 1}

{'Produces': {'stone_pickaxe': 1}, 'Requires': {'bench': True}, 'Consumes': {'cobble': 3, 'stick': 2}, 'Time': 1}

Let's reflect a little bit.

Do the recipe dictionaries in crafting.json all have the same set of keys? Print out a few to find out, or read the JSON file.

What is similar about the Initial, Goal, Produces, Consumes, and Requires schema? What is different about them?

Thinking back to your data structures class, what operations are involved in looking up a key in a dictionary data structure, and how does that compare to obtaining values from an array?

Look this up if you need to: What is the difference between a Python List of integers and a Python array.array of integers?

Let's proceed by using the same data representation for an inventory state (as in Initial), a partial inventory state (as in Goal, Produces, and Consumes), and the inventory query posed by Requires. Because the speed of the inner loop is of supreme importance in graph search, we'll want to use an array of unsigned integers data representation for each of these (this may feel like premature optimization; also try by comparing it with a simple dict-based representation). In order to go back and forth between item names and item indices, let's define a couple of global variables:

In [43]:

items_by_index = list(sorted(Crafting['Items']))

items_to_indices = {item: index for index, item in enumerate(items_by_index)}

We'll wrap our array-of-items data structure in a convenience class called State. Because we'll need to put States into closed sets, priority queues, and so on, we'll need them to be hashable, equatable, and comparable. We also will need to be adding and subtracting inventory states from each other later on, so we'll put that in there too. A skeleton of this class is provided below, with some of the data handling code given.

In [44]:

class State:

items: array.array

def __init__(self, items: Optional[Sequence[int]] = None) -> None:

if items is not None:

# Copying a state from an old state.

# This call to the array constructor creates an array of unsigned integers and initializes it from the contents of items.

self.items = array.array('I', items)

else:

self.items = array.array('I', [0 for item in items_by_index])

def __add__(self, other:'State')->'State':

s = State(self.items)

x = State(other.items)

for i,y in enumerate(items_by_index):

s.items[i] += x.items[i]

return s

def __sub__(self, other:'State')->'State':

s = State(self.items)

# TODO How do you subtract one state from another

x = State(other.items)

for i,j in enumerate(items_by_index):

s.items[i] -= x.items[i]

return s

def __ge__(self, other:'State')->bool:

s = State(self.items)

# TODO How do we know whether one state (self) contains everything that's inside of another (other)?

x = State(other.items)

for i,j in enumerate(items_by_index):

if(s.items[i] < x.items[i]):

return False

return True

def __lt__(self, other:'State')->bool:

return not (self >= other)

def __eq__(self, other:'State')->bool:

return self.items == other.items

def __hash__(self)->int:

hsh = 5381

for s in self.items:

hsh = ((hsh << 5) + hsh) + s

return hsh

def __str__(self)-> str:

out_str = []

for k,v in self.to_dict().items():

out_str.append('{}:{}'.format(k,v))

return ', '.join(out_str)

def to_dict(self) -> Dict[str, int]:

return {items_by_index[idx]: self.items[idx]

for idx in range(len(self.items))}

@classmethod

def from_dict(cls, item_dict: Dict[str, int]) -> 'State':

return cls([

item_dict.get(item, 0) for item in items_by_index

])

At this point we can already solve trivial problems:

In [45]:

# Example

initial = State.from_dict({'stone_pickaxe':1, 'ingot':2})

goal = State.from_dict({'ingot':1})

print()

print(initial)

print()

print(goal)

print()

print()

print(initial >= goal)

print(goal < initial)

print(goal >= initial)

bench:0, cart:0, coal:0, cobble:0, furnace:0, ingot:2, iron_axe:0, iron_pickaxe:0, ore:0, plank:0, rail:0, stick:0, stone_axe:0, stone_pickaxe:1, wood:0, wooden_axe:0, wooden_pickaxe:0

bench:0, cart:0, coal:0, cobble:0, furnace:0, ingot:1, iron_axe:0, iron_pickaxe:0, ore:0, plank:0, rail:0, stick:0, stone_axe:0, stone_pickaxe:0, wood:0, wooden_axe:0, wooden_pickaxe:0

True

True

False

Now that we have our state representation, we can rephrase the recipes in terms of what they need from the state.

Python has a useful datastructure -- namedtuple -- we can use for this purpose, so we'll have a namedtuple type called Recipe.

In [46]:

class Recipe(NamedTuple):

produces: State

consumes: State

requires: State

cost: int

It acts like a tuple in that its data are laid out contiguously in memory and it is immutable, but it has convenient accessors. Let's initialize a dictionary mapping names to recipes:

In [47]:

recipes: Dict[str, Recipe] = {}

for name, rule in Crafting['Recipes'].items():

recipes[name] = Recipe(

State.from_dict(rule.get('Produces', {})),

State.from_dict(rule.get('Consumes', {})),

State.from_dict({item: 1 if req else 0

for item, req in rule.get('Requires', {}).items()}),

rule['Time']

)

Now we have our state representation and our action representation for the crafting domain. Let's reflect.

What was the state representation in the path planning assignment?

What was the action representation?

How many possible actions are there in the whole domain, and how many of those are possible in a given state?

In fact, we can consider any planning problem in terms of states and a transition relation between states and those actions which are valid in that state. If we're thinking about path planning as search on the graph of possible locations (with edges given by a connectedness relation), task planning can be seen as search on the graph of possible states (with edges given by the state transition relation). Let's implement the transition relation now:

In [48]:

def preconditions_satisfied(state: State, recipe: Recipe) -> bool:

# TODO What needs to be true about state and recipe?

# Feel free to use State's >= method

consumes = recipe[1]

requires = recipe[2]

if(state >= requires and state >= consumes):

return True

return False

def apply_effects(state: State, recipe: Recipe) -> State:

#TODO What happens when a recipe is used?

produces = recipe[0]

consumes = recipe[1]

temp = state - consumes + produces

return temp

initial = State.from_dict({'stone_pickaxe':1, 'ingot':2})

print()

#Let's try using the pickaxe

if preconditions_satisfied(initial,recipes['stone_pickaxe for cobble']):

print('Stone Pickaxe',apply_effects(initial,recipes['stone_pickaxe for cobble']))

print()

if preconditions_satisfied(initial,recipes['wooden_pickaxe for cobble']):

print('Wooden Pickaxe',apply_effects(initial,recipes['wooden_pickaxe for cobble']))

Stone Pickaxe bench:0, cart:0, coal:0, cobble:1, furnace:0, ingot:2, iron_axe:0, iron_pickaxe:0, ore:0, plank:0, rail:0, stick:0, stone_axe:0, stone_pickaxe:1, wood:0, wooden_axe:0, wooden_pickaxe:0

**Planning via Graph Search**

Remember back to BFS. Consider how the one presented in class would need to change so it works on states-and-actions instead of locations-and-directions?

Let's try it:

In [55]:

def able_to_do(state:State) -> Tuple[List[State],List[str]]:

x = []

y = []

for recipe in recipes:

recipe_1 = recipes[recipe]

if(preconditions_satisfied(state,recipe_1)):

x.append(apply_effects(state,recipe_1))

y.append(recipe)

return (x,y)

test = State.from_dict({'stone_pickaxe':1, 'ingot':2})

#print(able_to_do(test))

hi

hi 2

herekkk

(419, ['craft stone_pickaxe at bench', 'wooden_pickaxe for cobble^^', 'wooden_pickaxe for coal^^', 'wooden_pickaxe for coal^*', 'wooden_pickaxe for coal^', 'craft wooden_pickaxe at bench^', 'craft bench^^^^', 'punch for wood^^^^^^^^', 'craft plank^^^^', 'punch for wood^^^^^^^', 'craft plank^^^', 'punch for wood^^^^^^', 'punch for wood^^^^^*', 'punch for wood^^^^^', 'punch for wood^^^^*', 'punch for wood^^^^', 'craft plank^^', 'punch for wood^^^', 'craft plank^', 'punch for wood^^', 'craft stick*', 'craft stick', 'craft plank', 'punch for wood', 'start'])

hi 2

herekkk

(10332, ['smelt ore in furnace', 'stone_pickaxe for cobble^^^^^^^^^^', 'stone_pickaxe for cobble^^^^^^^^^*', 'stone_pickaxe for cobble^^^^^^^^^', 'stone_pickaxe for cobble^^^^^^^^*', 'stone_pickaxe for cobble^^^^^^^^', 'stone_pickaxe for cobble^^^^^^^*', 'stone_pickaxe for cobble^^^^^^^', 'stone_pickaxe for coal^^^^^^', 'stone_pickaxe for ore^^^^^', 'stone_pickaxe for ore^^^^*', 'stone_pickaxe for ore^^^^', 'stone_pickaxe for ore^^^*', 'stone_pickaxe for ore^^^', 'stone_pickaxe for ore^^*', 'stone_pickaxe for ore^^', 'punch for wood^^', 'craft stick*', 'craft stick', 'craft plank', 'punch for wood', 'start'])

Imagine applying Dijkstra's here. How would things change? Image applying A* here. What heuristic would you want to use? Is that heuristic admissible? Is that a problem?

What's the largest planning problem (initial and goal state) you can think up which your BFS implementation can solve within 30 seconds? How many nodes does it visit and how long does it take in wall-clock time?

Planning with Iterated Widening

Let's compare against a dedicated planning algorithm, rather than applying graph search naively. Planning domains have some significant differences from general graph search problems---let's reflect on what they might be.

In graph search, what is the goal of a search? How is that different from the goal of a planning problem?

In graph search, what are the preconditions for traversing an edge? How does this differ in a planning problem?

In graph search, detecting cycles is relatively cheap. Is that the case for planning problems?

Is there more than one type of "cycle" in our crafting planning problem?

Think about a recipe like making a stone pickaxe. Every possible planning state either satisfies its preconditions or doesn't. If this recipe were the only action, we could formulate the problem as a domain with just three abstract states---one without a pickaxe and without the needed supplies, one without a pickaxe and with the needed supplies, and one with a pickaxe (and it doesn't matter what else is in the state).

If we had a domain with just two recipes (punch for wood and wood planks), what would be the abstract states in the sense used above?

We can automate the intuition of (15) by transforming our states into combinations of propositions A proposition here is a logical fact entailed by the state; for example

Step by Step Solution

There are 3 Steps involved in it

Step: 1

blur-text-image

Get Instant Access to Expert-Tailored Solutions

See step-by-step solutions with expert insights and AI powered tools for academic success

Step: 2

blur-text-image_2

Step: 3

blur-text-image_3

Ace Your Homework with AI

Get the answers you need in no time with our AI-driven, step-by-step assistance

Get Started

Recommended Textbook for

Database Concepts

Authors: David M. Kroenke, David J. Auer

7th edition

133544621, 133544626, 0-13-354462-1, 978-0133544626

More Books

Students also viewed these Databases questions