0% found this document useful (0 votes)
6 views

AI_Labfile

The document is a lab practical file for an Artificial Intelligence course, detailing various programming experiments related to solving the 8-Puzzle and 3-SAT problems using different algorithms. It includes implementations of the Generate and Test Strategy, DFID Strategy, Variable Neighbourhood Descent Algorithm, and Stochastic Hill Climbing Algorithm in Python. Each experiment outlines the aim, software used, overview, and code for the respective problem-solving approach.

Uploaded by

kumaracharyadev
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views

AI_Labfile

The document is a lab practical file for an Artificial Intelligence course, detailing various programming experiments related to solving the 8-Puzzle and 3-SAT problems using different algorithms. It includes implementations of the Generate and Test Strategy, DFID Strategy, Variable Neighbourhood Descent Algorithm, and Stochastic Hill Climbing Algorithm in Python. Each experiment outlines the aim, software used, overview, and code for the respective problem-solving approach.

Uploaded by

kumaracharyadev
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 36

MC312 – Artificial Intelligence

LAB PRACTICAL FILE

Submitted To :- Submitted By :-
Dr. Anshul Arora Nehal Ashraf
2K22/EC/156
E4 : P4 group
Sem - VI
INDEX
S. No. Topic Signature

1 Write a program to solve the 8-Puzzle problem using


Generate and Test Strategy

2 Write a program to solve the 8-Puzzle problem using


DFID Strategy

Write a program to solve the 3-SAT Problem using


3 Variable Neighbourhood Descent Algorithm

Write a program to solve the 3-SAT Problem using


4 Stochastic Hill Climbing Algorithm

Write a program to solve the 8-Puzzle problem using A*


5 algorithm

Write a program to implement AO* search algorithm on


6 any AND-OR graph

Write a program in Prolog to find maximum of


7 two/three numbers

Write a program in Prolog to find factorial of a number


8

Write a program in Prolog to find sum of first N numbers


9

Write a program in Prolog to find Fibonacci sequence


10 upto Nth term
Experiment – 1
Aim
Write a program to solve the 8-Puzzle problem using Generate and
Test Strategy

Software Used: Python

Overview
The 8-Puzzle problem is a classic sliding tile puzzle with a 3×3 grid
containing 8 numbered tiles and one empty space. The objective is to
rearrange the tiles from an initial configuration to a goal state by
sliding tiles into the empty space.
Generate and Test Strategy works by:
 Generating possible moves (sliding tiles into the empty space)
 Testing each move to see if it leads to the goal state
 If not, generating further moves
This approach systematically explores the state space without using
heuristics, typically implementing a breadth-first search to ensure
optimality. The solution must handle state representation, move
generation, goal testing, and path tracking.

Code
import copy
from collections import deque

def solve_8puzzle_generate_and_test(initial_state):
# Define the goal state
goal_state = [[1, 2, 3], [4, 5, 6], [7, 8, 0]] # 0 represents empty space

def print_puzzle(state):
for row in state:
print(row)
print()

def find_blank(state):
for i in range(3):
for j in range(3):
if state[i][j] == 0:
return i, j

def is_goal(state, goal):


return state == goal

def get_possible_moves(state):
i, j = find_blank(state)
possible_moves = []

# Check all four possible moves: up, down, left, right


if i > 0: # Up
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i-1][j] = new_state[i-1][j],
new_state[i][j]
possible_moves.append(new_state)

if i < 2: # Down
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i+1][j] = new_state[i+1][j],
new_state[i][j]
possible_moves.append(new_state)

if j > 0: # Left
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i][j-1] = new_state[i][j-1],
new_state[i][j]
possible_moves.append(new_state)

if j < 2: # Right
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i][j+1] = new_state[i][j+1],
new_state[i][j]
possible_moves.append(new_state)

return possible_moves

def generate_and_test_bfs(initial_state, goal_state,


max_iterations=10000):
queue = deque([(initial_state, [])]) # (state, path)
visited_states = set()
# Convert the initial state to a tuple of tuples to make it hashable
state_tuple = tuple(tuple(row) for row in initial_state)
visited_states.add(state_tuple)

iterations = 0

while queue and iterations < max_iterations:


current_state, path = queue.popleft()

if iterations % 1000 == 0:
print(f"Iteration {iterations}, Queue size: {len(queue)}")

if is_goal(current_state, goal_state):
print("Goal state reached!")
print(f"Solution found in {len(path)} moves and {iterations}
iterations.")
return path

possible_moves = get_possible_moves(current_state)

for new_state in possible_moves:


state_tuple = tuple(tuple(row) for row in new_state)
if state_tuple not in visited_states:
new_path = path + [new_state]
queue.append((new_state, new_path))
visited_states.add(state_tuple)

iterations += 1

print(f"Failed to find solution after {iterations} iterations.")


return None

print("8-Puzzle Problem - Generate and Test Strategy (BFS)")


print("Initial State:")
print_puzzle(initial_state)
print("Goal State:")
print_puzzle(goal_state)
print("Solving...")
solution_path = generate_and_test_bfs(initial_state, goal_state)

if solution_path:
print("Solution path:")
for i, state in enumerate(solution_path):
print(f"Move {i+1}:")
print_puzzle(state)

return solution_path
# Example usage
if __name__ == "__main__":
# Example initial state (easier to solve)
initial_state = [[1, 2, 3], [4, 0, 6], [7, 5, 8]]
solve_8puzzle_generate_and_test(initial_state)

Output
Experiment – 2
Aim
Write a program to solve the 8-Puzzle problem using DFID Strategy

Software Used: Python, VS-Code

Overview
Depth-First Iterative Deepening (DFID) combines the memory
efficiency of depth-first search with the completeness of breadth-
first search by:
 Starting with a depth limit of 1
 Performing depth-first search up to that limit
 If no solution is found, incrementing the depth limit and
repeating
For the 8-Puzzle, DFID is advantageous because:
 It finds the optimal solution (minimum number of moves)
 It uses less memory than breadth-first search
 It avoids the infinite path problems of standard depth-first
search
The implementation must include efficient state representation, loop
detection, and depth tracking mechanisms.

Code
import copy

def solve_8puzzle_dfid(initial_state):
# Define the goal state
goal_state = [[1, 2, 3], [4, 5, 6], [7, 8, 0]] # 0 represents empty space
def print_puzzle(state):
for row in state:
print(row)
print()

def find_blank(state):
for i in range(3):
for j in range(3):
if state[i][j] == 0:
return i, j

def is_goal(state, goal):


return state == goal

def get_possible_moves(state):
i, j = find_blank(state)
possible_moves = []

# Check all four possible moves: up, down, left, right


if i > 0: # Up
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i-1][j] = new_state[i-1][j],
new_state[i][j]
possible_moves.append(new_state)

if i < 2: # Down
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i+1][j] = new_state[i+1][j],
new_state[i][j]
possible_moves.append(new_state)

if j > 0: # Left
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i][j-1] = new_state[i][j-1],
new_state[i][j]
possible_moves.append(new_state)

if j < 2: # Right
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i][j+1] = new_state[i][j+1],
new_state[i][j]
possible_moves.append(new_state)

return possible_moves

def depth_limited_search(state, goal, depth_limit):


# Returns a tuple (found, solution_path)
def dls_recursive(state, goal, depth_limit, path, visited):
# Convert state to tuple for hashing
state_tuple = tuple(tuple(row) for row in state)

if is_goal(state, goal):
return True, path

if depth_limit <= 0:
return False, None

# Add current state to visited


visited.add(state_tuple)

possible_moves = get_possible_moves(state)

for new_state in possible_moves:


new_state_tuple = tuple(tuple(row) for row in new_state)
if new_state_tuple not in visited:
found, solution = dls_recursive(new_state, goal,
depth_limit - 1, path + [new_state], visited.copy())
if found:
return True, solution

return False, None

# Initial DLS call


return dls_recursive(state, goal, depth_limit, [state], set())

def dfid(initial_state, goal_state, max_depth=30):


for depth in range(1, max_depth + 1):
print(f"Searching with depth limit: {depth}")
found, solution = depth_limited_search(initial_state, goal_state,
depth)
if found:
print(f"Solution found at depth: {depth}")
return solution
print("No solution found within max depth.")
return None

print("8-Puzzle Problem - DFID Strategy")


print("Initial State:")
print_puzzle(initial_state)
print("Goal State:")
print_puzzle(goal_state)
print("Solving...")
solution_path = dfid(initial_state, goal_state)

if solution_path:
print("Solution path:")
for i, state in enumerate(solution_path):
print(f"Move {i}:")
print_puzzle(state)

return solution_path

# Example usage
if __name__ == "__main__":
# Example initial state (easier to solve)
initial_state = [[1, 2, 3], [4, 0, 6], [7, 5, 8]]
solve_8puzzle_dfid(initial_state)

Output
Experiment – 3
Aim
Write a program to solve the 3-SAT Problem using Variable
Neighbourhood Descent Algorithm

Software Used: Python, VS-Code

Overview
The 3-SAT Problem is a variant of the boolean satisfiability problem
where each clause contains exactly three literals. The goal is to
determine if there exists an assignment of truth values to variables
that makes the entire formula true.
Variable Neighbourhood Descent (VND) works by:
 Starting with an initial solution
 Systematically exploring increasingly distant neighborhoods
 Moving to better solutions when found
 Returning to closer neighborhoods when improvements are
made
For 3-SAT, this means:
 Representing boolean assignments as a solution
 Defining neighborhoods (e.g., flipping 1, 2, or 3 variables)
 Evaluating solutions by counting satisfied clauses
 Implementing a structured search across different
neighborhood types
The algorithm must balance exploration and exploitation to find
satisfying assignments efficiently.
Code
import random

def solve_3sat_vnd(clauses, variables):


"""
Solve a 3-SAT problem using Variable Neighborhood Descent.

Args:
clauses: List of tuples, where each tuple contains 3 integers.
Positive integers represent variables, negative integers
represent negated variables.
variables: Number of variables in the problem.

Returns:
A solution (assignment of values to variables) if found, None
otherwise.
"""
def evaluate(assignment, clause):
"""Evaluate if a clause is satisfied by an assignment."""
for literal in clause:
var = abs(literal)
if (literal > 0 and assignment[var - 1]) or (literal < 0 and not
assignment[var - 1]):
return True
return False

def count_satisfied_clauses(assignment, clauses):


"""Count the number of satisfied clauses."""
count = 0
for clause in clauses:
if evaluate(assignment, clause):
count += 1
return count

def flip_bit(assignment, index):


"""Flip a bit at the given index."""
new_assignment = assignment.copy()
new_assignment[index] = not new_assignment[index]
return new_assignment

def neighborhood_1(assignment):
"""Return all assignments with one bit flipped."""
neighbors = []
for i in range(variables):
neighbors.append(flip_bit(assignment, i))
return neighbors
def neighborhood_2(assignment):
"""Return all assignments with two consecutive bits flipped."""
neighbors = []
for i in range(variables - 1):
new_assignment = assignment.copy()
new_assignment[i] = not new_assignment[i]
new_assignment[i + 1] = not new_assignment[i + 1]
neighbors.append(new_assignment)
return neighbors

def neighborhood_3(assignment):
"""Return some random assignments with three bits flipped."""
neighbors = []
for _ in range(min(50, variables * (variables - 1) * (variables - 2)
// 6)):
indices = random.sample(range(variables), 3)
new_assignment = assignment.copy()
for i in indices:
new_assignment[i] = not new_assignment[i]
neighbors.append(new_assignment)
return neighbors

def vnd_search(initial_assignment):
"""Perform VND search."""
neighborhoods = [neighborhood_1, neighborhood_2, neighborhood_3]
current_assignment = initial_assignment
current_score = count_satisfied_clauses(current_assignment, clauses)

if current_score == len(clauses):
return current_assignment

improved = True
while improved:
improved = False
for get_neighbors in neighborhoods:
neighbors = get_neighbors(current_assignment)
best_neighbor = None
best_score = current_score

for neighbor in neighbors:


score = count_satisfied_clauses(neighbor, clauses)
if score > best_score:
best_score = score
best_neighbor = neighbor

if best_score > current_score:


current_assignment = best_neighbor
current_score = best_score
improved = True

if current_score == len(clauses):
return current_assignment

break # Restart with the first neighborhood

return None if current_score < len(clauses) else current_assignment

# Try multiple random initial assignments


for _ in range(10):
initial_assignment = [random.choice([True, False]) for _ in
range(variables)]
solution = vnd_search(initial_assignment)
if solution:
return solution

return None

# Example usage
if __name__ == "__main__":
# Example 3-SAT problem: (x1 OR x2 OR x3) AND (NOT x1 OR x2 OR x3) AND (x1
OR NOT x2 OR NOT x3)
# Represented as [(1, 2, 3), (-1, 2, 3), (1, -2, -3)]
clauses = [(1, 2, 3), (-1, 2, 3), (1, -2, -3)]
num_variables = 3

solution = solve_3sat_vnd(clauses, num_variables)

if solution:
print("Solution found!")
for i, val in enumerate(solution):
print(f"x{i+1} = {val}")

# Verify the solution


all_satisfied = True
for clause in clauses:
satisfied = False
for literal in clause:
var = abs(literal)
if (literal > 0 and solution[var - 1]) or (literal < 0 and not
solution[var - 1]):
satisfied = True
break
if not satisfied:
all_satisfied = False
print(f"Clause {clause} is not satisfied!")
if all_satisfied:
print("All clauses are satisfied.")
else:
print("No solution found.")

Output
Experiment – 4
Aim
Write a program to solve the 3-SAT Problem using Stochastic Hill
Climbing Algorithm

Software Used: Python, VS-Code

Overview
Stochastic Hill Climbing is a variant of hill climbing that introduces
randomness to escape local optima. For the 3-SAT problem, it works
by:
 Starting with a random assignment of truth values
 Randomly selecting a variable to flip
 Accepting the change if it improves the solution (increases
satisfied clauses)
 Sometimes accepting non-improving moves with a certain
probability
The implementation should include:
 Random solution initialization
 Variable selection strategy
 Evaluation function to count satisfied clauses
 Acceptance criteria that allows occasional "bad" moves
 Termination conditions (solution found or iteration limit
reached)
This approach is particularly useful for large 3-SAT instances where
complete methods are impractical.
Code
import random

def solve_3sat_stochastic_hill_climbing(clauses, variables,


max_iterations=10000):
"""
Solve a 3-SAT problem using Stochastic Hill Climbing.

Args:
clauses: List of tuples, where each tuple contains 3 integers.
Positive integers represent variables, negative integers
represent negated variables.
variables: Number of variables in the problem.
max_iterations: Maximum number of iterations to try.

Returns:
A solution (assignment of values to variables) if found, None
otherwise.
"""
def evaluate(assignment, clause):
"""Evaluate if a clause is satisfied by an assignment."""
for literal in clause:
var = abs(literal)
if (literal > 0 and assignment[var - 1]) or (literal < 0 and not
assignment[var - 1]):
return True
return False

def count_satisfied_clauses(assignment, clauses):


"""Count the number of satisfied clauses."""
count = 0
for clause in clauses:
if evaluate(assignment, clause):
count += 1
return count

def flip_bit(assignment, index):


"""Flip a bit at the given index."""
new_assignment = assignment.copy()
new_assignment[index] = not new_assignment[index]
return new_assignment

def get_random_neighbor(assignment):
"""Get a random neighbor by flipping one bit."""
index = random.randint(0, variables - 1)
return flip_bit(assignment, index)
def stochastic_hill_climbing():
"""Perform stochastic hill climbing."""
# Generate random initial assignment
current_assignment = [random.choice([True, False]) for _ in
range(variables)]
current_score = count_satisfied_clauses(current_assignment, clauses)

if current_score == len(clauses):
return current_assignment

for iteration in range(max_iterations):


if iteration % 1000 == 0:
print(f"Iteration {iteration}, Current score:
{current_score}/{len(clauses)}")

# Generate a random neighbor


neighbor = get_random_neighbor(current_assignment)
neighbor_score = count_satisfied_clauses(neighbor, clauses)

# If the neighbor is better or equally good, move to it


if neighbor_score >= current_score:
current_assignment = neighbor
current_score = neighbor_score

if current_score == len(clauses):
print(f"Solution found at iteration {iteration}")
return current_assignment

print(f"No solution found after {max_iterations} iterations.")


return None

# Try multiple runs of stochastic hill climbing


for run in range(5):
print(f"Run {run+1}/5")
solution = stochastic_hill_climbing()
if solution:
return solution

return None

# Example usage
if __name__ == "__main__":
# Example 3-SAT problem: (x1 OR x2 OR x3) AND (NOT x1 OR x2 OR x3) AND (x1
OR NOT x2 OR NOT x3)
# Represented as [(1, 2, 3), (-1, 2, 3), (1, -2, -3)]
clauses = [(1, 2, 3), (-1, 2, 3), (1, -2, -3)]
num_variables = 3
solution = solve_3sat_stochastic_hill_climbing(clauses, num_variables)

if solution:
print("Solution found!")
for i, val in enumerate(solution):
print(f"x{i+1} = {val}")

# Verify the solution


all_satisfied = True
for clause in clauses:
satisfied = False
for literal in clause:
var = abs(literal)
if (literal > 0 and solution[var - 1]) or (literal < 0 and not
solution[var - 1]):
satisfied = True
break
if not satisfied:
all_satisfied = False
print(f"Clause {clause} is not satisfied!")

if all_satisfied:
print("All clauses are satisfied.")
else:
print("No solution found.")

Output
Experiment – 5
Aim
Write a program to solve the 8-Puzzle problem using A* algorithm

Software Used: Python, VS-Code

Overview
The A algorithm* is an informed search strategy that evaluates
nodes by combining:
 g(n): The cost to reach the node from the start
 h(n): A heuristic estimate of the cost to reach the goal from the
node
For the 8-Puzzle, common heuristics include:
 Manhattan distance: Sum of the distances each tile is from its
goal position
 Misplaced tiles: Number of tiles not in their goal position
The implementation must include:
 Priority queue based on f(n) = g(n) + h(n)
 State representation and operators
 Closed set to avoid revisiting states
 Path reconstruction to return the solution
A* guarantees an optimal solution if the heuristic is admissible
(never overestimates the true cost).
Code
import heapq
import copy

def solve_8puzzle_astar(initial_state):
"""
Solve the 8-puzzle problem using A* algorithm.

Args:
initial_state: 3x3 grid represented as a list of lists.
0 represents the empty space.

Returns:
A solution path if found, None otherwise.
"""
# Define the goal state
goal_state = [[1, 2, 3], [4, 5, 6], [7, 8, 0]]

def print_puzzle(state):
for row in state:
print(row)
print()

def find_position(state, value):


"""Find the position of a value in the state."""
for i in range(3):
for j in range(3):
if state[i][j] == value:
return i, j
return None

def manhattan_distance(state, goal):


"""Calculate Manhattan distance heuristic."""
distance = 0
for i in range(3):
for j in range(3):
value = state[i][j]
if value != 0: # Skip the empty space
goal_i, goal_j = find_position(goal, value)
distance += abs(i - goal_i) + abs(j - goal_j)
return distance

def get_possible_moves(state):
"""Get all possible moves from the current state."""
moves = []
i, j = find_position(state, 0) # Find the empty space
# Try moving the empty space in all four directions
if i > 0: # Up
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i-1][j] = new_state[i-1][j],
new_state[i][j]
moves.append(new_state)

if i < 2: # Down
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i+1][j] = new_state[i+1][j],
new_state[i][j]
moves.append(new_state)

if j > 0: # Left
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i][j-1] = new_state[i][j-1],
new_state[i][j]
moves.append(new_state)

if j < 2: # Right
new_state = copy.deepcopy(state)
new_state[i][j], new_state[i][j+1] = new_state[i][j+1],
new_state[i][j]
moves.append(new_state)

return moves

def state_to_tuple(state):
"""Convert state to tuple for hashability."""
return tuple(tuple(row) for row in state)

def astar_search():
"""Perform A* search."""
# Priority queue of states to explore: (f-value, g-value, state, path)
# f-value = g-value + h-value (path cost + heuristic)
# g-value = path cost (number of moves)
open_set = [(manhattan_distance(initial_state, goal_state), 0,
initial_state, [initial_state])]
closed_set = set() # Set of visited states

iterations = 0

while open_set and iterations < 50000:


iterations += 1

if iterations % 1000 == 0:
print(f"Iteration {iterations}, Open set size:
{len(open_set)}")
# Pop the state with the lowest f-value
f, g, current_state, path = heapq.heappop(open_set)

# Check if we've reached the goal


if current_state == goal_state:
print(f"Solution found in {g} moves and {iterations}
iterations.")
return path

# Skip if we've already visited this state


current_tuple = state_to_tuple(current_state)
if current_tuple in closed_set:
continue

# Add current state to closed set


closed_set.add(current_tuple)

# Explore next possible moves


for next_state in get_possible_moves(current_state):
next_tuple = state_to_tuple(next_state)

if next_tuple in closed_set:
continue

next_g = g + 1 # Increment path cost


next_h = manhattan_distance(next_state, goal_state) #
Calculate heuristic
next_f = next_g + next_h # Calculate f-value

# Add to open set


heapq.heappush(open_set, (next_f, next_g, next_state, path +
[next_state]))

print(f"No solution found after {iterations} iterations.")


return None

print("8-Puzzle Problem - A* Algorithm")


print("Initial State:")
print_puzzle(initial_state)
print("Goal State:")
print_puzzle(goal_state)
print("Solving...")

solution_path = astar_search()

if solution_path:
print("Solution path:")
for i, state in enumerate(solution_path):
print(f"Move {i}:")
print_puzzle(state)

return solution_path

# Example usage
if __name__ == "__main__":
# Example initial state (easier to solve)
initial_state = [[1, 2, 3], [4, 0, 6], [7, 5, 8]]
solve_8puzzle_astar(initial_state)

Output
Experiment – 6
Aim
Write a program to implement AO* search algorithm on any AND-OR
graph

Software Used: Python, VS-Code

Overview
AO Search* is an extension of A* for solving problems that can be
represented as AND-OR graphs, where:
 OR nodes: Represent alternative ways to solve a problem (only
one needs to succeed)
 AND nodes: Represent subproblems that must all be solved
The algorithm works by:
 Expanding the most promising paths first
 Computing the cost of nodes based on their type (AND/OR)
 Propagating costs up the graph
 Marking the best partial solution graph
The implementation should include:
 Graph representation with AND/OR nodes
 Heuristic evaluation function
 Cost propagation mechanisms
 Solution graph extraction
This approach is useful for problem-solving in domains like planning,
game playing, and theorem proving.
Code
class Node:
def __init__(self, name, is_and_node=False, cost=0, heuristic=0):
self.name = name
self.is_and_node = is_and_node # True for AND nodes, False for OR
nodes
self.cost = cost # Cost to reach this node
self.heuristic = heuristic # Heuristic estimate to reach goal
self.children = [] # List of child nodes
self.marked = False # Whether this node is marked (part of the
solution)
self.solved = False # Whether this node is solved
self.value = float('inf') # Value of the node (cost + heuristic)

def __repr__(self):
return f"Node({self.name}, {'AND' if self.is_and_node else 'OR'},
cost={self.cost}, h={self.heuristic}, value={self.value},
solved={self.solved})"

def solve_ao_star(graph, root):


"""
Solve a problem using AO* algorithm on an AND-OR graph.

Args:
graph: Dictionary mapping node names to Node objects.
root: The root node of the graph.

Returns:
The solution graph (marked nodes).
"""
def calculate_node_value(node):
"""Calculate the value of a node based on its children."""
if not node.children:
# Terminal node
node.value = node.cost
node.solved = True
return node.value

if node.is_and_node:
# AND node: sum of all children's values
total_value = 0
all_solved = True
for child in node.children:
total_value += child.value
all_solved = all_solved and child.solved
node.value = node.cost + total_value
node.solved = all_solved
return node.value
else:
# OR node: minimum value among children
min_value = float('inf')
node.solved = False
for child in node.children:
if child.value < min_value:
min_value = child.value
node.solved = child.solved
node.value = node.cost + min_value
return node.value

def mark_best_path(node):
"""Mark the best path in the graph."""
node.marked = True

if not node.children:
return

if node.is_and_node:
# Mark all children of AND node
for child in node.children:
mark_best_path(child)
else:
# Mark only the best child of OR node
best_child = None
min_value = float('inf')
for child in node.children:
if child.value < min_value:
min_value = child.value
best_child = child
if best_child:
mark_best_path(best_child)

def expand_graph(node):
"""Expand the graph from the given node."""
if node.solved or not node.children:
return

# Recursively expand all children


for child in node.children:
expand_graph(child)

# Update node's value


calculate_node_value(node)

# Initialize the graph


root.value = root.cost + root.heuristic
# Expand the graph and calculate values
expand_graph(root)

# Mark the best path


mark_best_path(root)

return graph

# Example usage
if __name__ == "__main__":
# Create a simple AND-OR graph
# A is an OR node with children B and C
# B is an AND node with children D and E
# C is an OR node with child F
# D, E, F are terminal nodes

# Create nodes
A = Node("A", is_and_node=False, cost=0, heuristic=5)
B = Node("B", is_and_node=True, cost=2, heuristic=3)
C = Node("C", is_and_node=False, cost=3, heuristic=2)
D = Node("D", is_and_node=False, cost=4, heuristic=0)
E = Node("E", is_and_node=False, cost=5, heuristic=0)
F = Node("F", is_and_node=False, cost=7, heuristic=0)

# Set up the graph


A.children = [B, C]
B.children = [D, E]
C.children = [F]

# Create the graph dictionary


graph = {"A": A, "B": B, "C": C, "D": D, "E": E, "F": F}

# Solve the graph using AO*


solved_graph = solve_ao_star(graph, A)

# Print the solution


print("AO* Solution:")
for name, node in solved_graph.items():
if node.marked:
print(f"Node {name} is part of the solution (value={node.value},
solved={node.solved})")

Output
Experiment – 7
Aim
Write a program in Prolog to find maximum of two/three numbers

Software Used: Swipl-Prolog,VsCode


Overview
This task requires implementing logical rules in Prolog to determine
the maximum value among either two or three numbers. The
solution involves:
 Defining predicates using pattern matching and logical
conditions
 Creating rules that compare two numbers and return the larger
one
 Extending the approach to handle three numbers by finding the
maximum of two, then comparing with the third
The implementation demonstrates Prolog's declarative paradigm,
where rules define relationships between inputs and outputs rather
than specifying a sequence of steps.
Example approach:
prolog
max_of_two(X, Y, X) :- X >= Y.
max_of_two(X, Y, Y) :- X < Y.

max_of_three(X, Y, Z, Max) :-
max_of_two(X, Y, MaxXY),
max_of_two(MaxXY, Z, Max).

Code
% Find maximum of two numbers
max_of_two(X, Y, Max) :-
X >= Y,
Max is X.
max_of_two(X, Y, Max) :-
X < Y,
Max is Y.

% Find maximum of three numbers


max_of_three(X, Y, Z, Max) :-
max_of_two(X, Y, MaxXY),
max_of_two(MaxXY, Z, Max).

Output
Experiment – 8
Aim
Write a program in Prolog to find factorial of a number

Software Used: Swipl-Prolog,VsCode

Overview
This question requires implementing factorial calculation in Prolog's
logical programming paradigm. The implementation includes:
 Base case: Factorial of 0 is 1
 Recursive case: Factorial of N is N multiplied by factorial of (N-
1)
The solution demonstrates:
 Prolog's powerful pattern matching for base case detection
 Recursive rule definition
 Arithmetic expressions using the is operator
This program serves as an excellent example of how mathematical
recursion translates naturally to Prolog's declarative style.

Code
% Base case: factorial of 0 is 1
factorial(0, 1).

% Recursive case: factorial of N is N * factorial(N-1)


factorial(N, Result) :-
N > 0,
N1 is N - 1,
factorial(N1, SubResult),
Result is N * SubResult.
Output
Experiment – 9
Aim
Write a program in Prolog to find sum of first N numbers

Software Used: Swipl-Prolog,VsCode

Overview
This task involves creating a Prolog program to calculate the sum of
integers from 1 to N. The implementation includes:
 Base case: Sum of first 0 numbers is 0
 Recursive case: Sum of first N numbers is N plus the sum of first
(N-1) numbers
Key concepts demonstrated include:
 Recursive rule definition
 Arithmetic in Prolog
 Base case handling to terminate recursion
The solution shows how Prolog's logical paradigm can be used to
express mathematical series calculations.

Code
% Base case: sum of first 0 numbers is 0
sum_first_n(0, 0).

% Recursive case: sum of first N numbers is N + sum of first (N-1) numbers


sum_first_n(N, Sum) :-
N > 0,
N1 is N - 1,
sum_first_n(N1, SubSum),
Sum is N + SubSum.
Output
Experiment – 10
Aim
Write a program in Prolog to find Fibonacci sequence upto Nth term

Software Used: Swipl-Prolog,VsCode

Overview
This task requires implementing the Fibonacci sequence in Prolog,
where each number is the sum of the two preceding ones (starting
with 0 and 1). The implementation includes:
 Base cases: Fibonacci(0) = 0, Fibonacci(1) = 1
 Recursive case: Fibonacci(N) = Fibonacci(N-1) + Fibonacci(N-2)
 A procedure to print all Fibonacci numbers up to the Nth term
The solution demonstrates:
 Multiple base cases in Prolog
 Arithmetic calculations
 Recursive definitions
 Potentially, memoization techniques to improve performance
This program showcases Prolog's ability to express mathematical
sequences through logical rules and recursion.

Code
% Base cases
fibonacci(0, 0).
fibonacci(1, 1).

% Recursive case
fibonacci(N, Result) :-
N > 1,
N1 is N - 1,
N2 is N - 2,
fibonacci(N1, F1),
fibonacci(N2, F2),
Result is F1 + F2.

% Predicate to print Fibonacci sequence up to Nth term


print_fibonacci_up_to(N) :-
print_fibonacci_up_to(0, N).

print_fibonacci_up_to(Current, N) :-
Current =< N,
fibonacci(Current, F),
format('Fibonacci(~w) = ~w~n', [Current, F]),
Next is Current + 1,
print_fibonacci_up_to(Next, N).
print_fibonacci_up_to(Current, N) :-
Current > N.

Output

You might also like