Skip to content

Commit

Permalink
Add CSP functionality to the library
Browse files Browse the repository at this point in the history
Add the ability to solve constraint satisfaction problems (CSPs) to the library.

* **Definitions and Search Implementation**
  - Add `CSPProblem` class in `include/definitions.h` to handle variables, domains, and constraints.
  - Add `BacktrackingSearch` class in `include/definitions.h` for solving CSPs using backtracking search algorithm.
  - Implement methods for `CSPProblem` and `BacktrackingSearch` classes in `src/search.cpp`.

* **Example and Tests**
  - Add example CSP problem in `examples/csp_example.cpp` demonstrating the usage of `CSPProblem` and `BacktrackingSearch`.
  - Add test cases for CSP functionality in `tests/tests.cpp` to ensure correctness and performance.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/velocitatem/Symphony?shareId=XXXX-XXXX-XXXX-XXXX).
  • Loading branch information
velocitatem committed Dec 16, 2024
1 parent f3d7182 commit c612f5d
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 0 deletions.
40 changes: 40 additions & 0 deletions examples/csp_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include "definitions.h"
#include <iostream>
#include <unordered_map>

int main() {
// Create a CSP problem
CSPProblem csp;

// Add variables and their domains
csp.add_variable("X", {1, 2, 3});
csp.add_variable("Y", {1, 2, 3});
csp.add_variable("Z", {1, 2, 3});

// Add constraints
csp.add_constraint([](const std::unordered_map<std::string, int> &assignment) {
return assignment.at("X") != assignment.at("Y");
});
csp.add_constraint([](const std::unordered_map<std::string, int> &assignment) {
return assignment.at("Y") != assignment.at("Z");
});
csp.add_constraint([](const std::unordered_map<std::string, int> &assignment) {
return assignment.at("X") != assignment.at("Z");
});

// Solve the CSP using backtracking search
BacktrackingSearch search(&csp);
std::unordered_map<std::string, int> solution = search.search();

// Print the solution
if (!solution.empty()) {
std::cout << "Solution found:" << std::endl;
for (const auto &pair : solution) {
std::cout << pair.first << " = " << pair.second << std::endl;
}
} else {
std::cout << "No solution found." << std::endl;
}

return 0;
}
138 changes: 138 additions & 0 deletions include/definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
#include <unordered_set>

/**
* @brief Represents an abstract state in a problem.
Expand Down Expand Up @@ -113,4 +115,140 @@ class Problem {
State *initial_state_;
};

/**
* @brief Represents a constraint satisfaction problem (CSP).
*
* This class defines the structure of a CSP, including variables, domains, and constraints.
* It should be extended by the user to implement specific CSP logic.
*/
class CSPProblem : public Problem {
public:
/**
* @brief Constructor for the CSPProblem class.
*/
CSPProblem() {}

/**
* @brief Virtual destructor for the CSPProblem class.
*/
virtual ~CSPProblem() {}

/**
* @brief Adds a variable to the CSP.
*
* @param variable The name of the variable.
* @param domain The domain of the variable.
*/
void add_variable(const std::string &variable, const std::vector<int> &domain) {
variables_[variable] = domain;
}

/**
* @brief Adds a constraint to the CSP.
*
* @param constraint The constraint function.
*/
void add_constraint(const std::function<bool(const std::unordered_map<std::string, int> &)> &constraint) {
constraints_.push_back(constraint);
}

/**
* @brief Tests if a given assignment satisfies all constraints.
*
* @param assignment The assignment to test.
* @return True if the assignment satisfies all constraints, false otherwise.
*/
bool is_consistent(const std::unordered_map<std::string, int> &assignment) const {
for (const auto &constraint : constraints_) {
if (!constraint(assignment)) {
return false;
}
}
return true;
}

/**
* @brief Retrieves the variables of the CSP.
*
* @return A reference to the variables of the CSP.
*/
const std::unordered_map<std::string, std::vector<int>> &variables() const {
return variables_;
}

private:
std::unordered_map<std::string, std::vector<int>> variables_; ///< Variables of the CSP.
std::vector<std::function<bool(const std::unordered_map<std::string, int> &)>> constraints_; ///< Constraints of the CSP.
};

/**
* @brief Represents a backtracking search algorithm for solving CSPs.
*/
class BacktrackingSearch {
public:
/**
* @brief Constructor for the BacktrackingSearch class.
*
* @param problem The CSP problem to solve.
*/
BacktrackingSearch(CSPProblem *problem) : problem_(problem) {}

/**
* @brief Solves the CSP using backtracking search.
*
* @return A solution to the CSP, or an empty assignment if no solution is found.
*/
std::unordered_map<std::string, int> search() {
std::unordered_map<std::string, int> assignment;
if (backtrack(assignment)) {
return assignment;
} else {
return {};
}
}

private:
/**
* @brief Performs backtracking search to find a solution to the CSP.
*
* @param assignment The current assignment.
* @return True if a solution is found, false otherwise.
*/
bool backtrack(std::unordered_map<std::string, int> &assignment) {
if (assignment.size() == problem_->variables().size()) {
return true;
}

std::string variable = select_unassigned_variable(assignment);
for (int value : problem_->variables().at(variable)) {
assignment[variable] = value;
if (problem_->is_consistent(assignment)) {
if (backtrack(assignment)) {
return true;
}
}
assignment.erase(variable);
}

return false;
}

/**
* @brief Selects an unassigned variable from the CSP.
*
* @param assignment The current assignment.
* @return The name of an unassigned variable.
*/
std::string select_unassigned_variable(const std::unordered_map<std::string, int> &assignment) const {
for (const auto &variable : problem_->variables()) {
if (assignment.find(variable.first) == assignment.end()) {
return variable.first;
}
}
return "";
}

CSPProblem *problem_; ///< The CSP problem to solve.
};

#endif // DEFINITIONS_H
62 changes: 62 additions & 0 deletions src/search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,65 @@ std::shared_ptr<Node> AStarSearch::search() {
// Return nullptr if no solution is found
return nullptr;
}

// Implement CSPProblem class methods for handling variables, domains, and constraints
void CSPProblem::add_variable(const std::string &variable, const std::vector<int> &domain) {
variables_[variable] = domain;
}

void CSPProblem::add_constraint(const std::function<bool(const std::unordered_map<std::string, int> &)> &constraint) {
constraints_.push_back(constraint);
}

bool CSPProblem::is_consistent(const std::unordered_map<std::string, int> &assignment) const {
for (const auto &constraint : constraints_) {
if (!constraint(assignment)) {
return false;
}
}
return true;
}

const std::unordered_map<std::string, std::vector<int>> &CSPProblem::variables() const {
return variables_;
}

// Implement BacktrackingSearch class methods for solving CSPs using backtracking search algorithm
BacktrackingSearch::BacktrackingSearch(CSPProblem *problem) : problem_(problem) {}

std::unordered_map<std::string, int> BacktrackingSearch::search() {
std::unordered_map<std::string, int> assignment;
if (backtrack(assignment)) {
return assignment;
} else {
return {};
}
}

bool BacktrackingSearch::backtrack(std::unordered_map<std::string, int> &assignment) {
if (assignment.size() == problem_->variables().size()) {
return true;
}

std::string variable = select_unassigned_variable(assignment);
for (int value : problem_->variables().at(variable)) {
assignment[variable] = value;
if (problem_->is_consistent(assignment)) {
if (backtrack(assignment)) {
return true;
}
}
assignment.erase(variable);
}

return false;
}

std::string BacktrackingSearch::select_unassigned_variable(const std::unordered_map<std::string, int> &assignment) const {
for (const auto &variable : problem_->variables()) {
if (assignment.find(variable.first) == assignment.end()) {
return variable.first;
}
}
return "";
}
46 changes: 46 additions & 0 deletions tests/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,52 @@ TEST(Search, AStarSearch) {
delete search;
}

// Test case for CSPProblem class
TEST(CSPProblem, AddVariable) {
CSPProblem csp;
csp.add_variable("X", {1, 2, 3});
EXPECT_EQ(csp.variables().size(), 1);
EXPECT_EQ(csp.variables().at("X").size(), 3);
}

TEST(CSPProblem, AddConstraint) {
CSPProblem csp;
csp.add_variable("X", {1, 2, 3});
csp.add_variable("Y", {1, 2, 3});
csp.add_constraint([](const std::unordered_map<std::string, int> &assignment) {
return assignment.at("X") != assignment.at("Y");
});
std::unordered_map<std::string, int> assignment = {{"X", 1}, {"Y", 2}};
EXPECT_TRUE(csp.is_consistent(assignment));
assignment = {{"X", 1}, {"Y", 1}};
EXPECT_FALSE(csp.is_consistent(assignment));
}

// Test case for BacktrackingSearch class
TEST(BacktrackingSearch, Search) {
CSPProblem csp;
csp.add_variable("X", {1, 2, 3});
csp.add_variable("Y", {1, 2, 3});
csp.add_variable("Z", {1, 2, 3});
csp.add_constraint([](const std::unordered_map<std::string, int> &assignment) {
return assignment.at("X") != assignment.at("Y");
});
csp.add_constraint([](const std::unordered_map<std::string, int> &assignment) {
return assignment.at("Y") != assignment.at("Z");
});
csp.add_constraint([](const std::unordered_map<std::string, int> &assignment) {
return assignment.at("X") != assignment.at("Z");
});

BacktrackingSearch search(&csp);
std::unordered_map<std::string, int> solution = search.search();

EXPECT_FALSE(solution.empty());
EXPECT_NE(solution["X"], solution["Y"]);
EXPECT_NE(solution["Y"], solution["Z"]);
EXPECT_NE(solution["X"], solution["Z"]);
}

int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
Expand Down

0 comments on commit c612f5d

Please sign in to comment.