diff --git a/.gitmodules b/.gitmodules index b64f4e54..6787c4a2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "submodules/lydia"] path = submodules/lydia - url = https://github.com/whitemech/lydia.git + url = https://github.com/GianmarcoDIAG/lydia.git [submodule "submodules/slugs"] path = submodules/slugs url = https://github.com/VerifiableRobotics/slugs diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..6d4118f5 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/src/synthesis/header" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c17", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index b15fcba8..67225c9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,10 @@ project(LydiaSyft) include(FetchContent) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_INSTALL_PREFIX /usr/local) @@ -31,7 +34,6 @@ if (NOT DEFINED MONA_USE_STATIC_LIBS) endif() find_package(mona REQUIRED) - if (NOT DEFINED Z3_FETCH) set(Z3_FETCH ON) endif() @@ -85,14 +87,14 @@ set(EXT_INCLUDE_PATH ${LYDIA_INCLUDE_DIR} ${LYDIA_THIRD_PARTY_INCLUDE_PATH} ${CU message(STATUS EXT_LIBRARIES_PATH ${EXT_LIBRARIES_PATH}) -enable_testing() +# enable_testing() add_subdirectory(src) -if (LYDIASYFT_ENABLE_TESTS) - add_subdirectory(test) -endif() +#if (LYDIASYFT_ENABLE_TESTS) + add_subdirectory(test) +# endif() -if (LYDIASYFT_ENABLE_EXAMPLES) +# if (LYDIASYFT_ENABLE_EXAMPLES) add_subdirectory(examples) -endif() +# endif() diff --git a/examples/01_quickstart/quickstart.cpp b/examples/01_quickstart/quickstart.cpp index a5974f51..d8585903 100644 --- a/examples/01_quickstart/quickstart.cpp +++ b/examples/01_quickstart/quickstart.cpp @@ -1,57 +1,98 @@ +#include +#include +#include "Parser.h" +#include "VarMgr.h" #include #include #include #include - +#include +#include "lydia/mona_ext/mona_ext_base.hpp" #include - #include "automata/ExplicitStateDfa.h" #include "automata/ExplicitStateDfaAdd.h" #include "automata/SymbolicStateDfa.h" #include "game/InputOutputPartition.h" #include "Player.h" -#include "VarMgr.h" #include "synthesizer/LTLfSynthesizer.h" int main(int argc, char ** argv) { - - // define the formula and the input/output variables - std::string formula_str = "F(a | b)"; - std::vector input_vars{"a"}; - std::vector output_vars{"b"}; + std::string partition_file = "/examples/01_quickstart/test_vars.part"; + std::cout << "Open file: " << partition_file << std::endl; - // parse the formula - auto driver = std::make_shared(); - std::stringstream formula_stream(formula_str); - driver->parse(formula_stream); - whitemech::lydia::ltlf_ptr formula = driver->get_result(); + + Syft::InputOutputPartition partition = Syft::InputOutputPartition::read_from_file(partition_file); - // initialize the variables - Syft::InputOutputPartition partition = Syft::InputOutputPartition::construct_from_input(input_vars, output_vars); - std::shared_ptr var_mgr = std::make_shared(); - var_mgr->create_named_variables(partition.input_variables); - var_mgr->create_named_variables(partition.output_variables); + auto input = partition.input_variables; + auto agents = partition.agent_variables; + + std::vector formulas = { + "F(x & a)", // Agent 0 + "G(y -> b)", // Agent 1 + "F(a & b & c)" // Agent 2 + }; + std::cout << "Input variables found: " << input.size() << std::endl; + std::cout << "Number of agents found: " << agents.size() << std::endl; + std::cout << "Agent formulas found: " << formulas.size() << std::endl; - // build the explicit-state DFA - Syft::ExplicitStateDfa explicit_dfa = Syft::ExplicitStateDfa::dfa_of_formula(*formula); - Syft::ExplicitStateDfaAdd explicit_dfa_add = Syft::ExplicitStateDfaAdd::from_dfa_mona(var_mgr, explicit_dfa); + std::cout << "Input variables: " << std::endl; + for (const auto& var : input) { + std::cout << var << std::endl; + } + for (std::size_t i = 0; i < agents.size(); ++i) { + std::cout << "Agent " << i << " variables: " << std::endl; + for (const auto& var : agents[i]) { + std::cout << var << std::endl; + } + std::cout << "Agent " << i << " formula: " << formulas[i] << std::endl; + } + + //set up VarMgr + std::shared_ptr var_mgr = std::make_shared(); + var_mgr->create_named_variables(input); + for (const auto& agent_vars : agents) { + var_mgr->create_named_variables(agent_vars); + } + var_mgr->partition_variables(input, agents); - // build the symbolic-state DFA from the explicit-state DFA - Syft::SymbolicStateDfa symbolic_dfa = Syft::SymbolicStateDfa::from_explicit( - std::move(explicit_dfa_add)); + //Directory for showing results + std::string out_dir = "dfa_outputs"; + std::filesystem::remove_all(out_dir); + std::filesystem::create_directories(out_dir); + auto driver = std::make_shared(); - // do synthesis - var_mgr->partition_variables(partition.input_variables, partition.output_variables); - Syft::Player starting_player = Syft::Player::Agent; - Syft::Player protagonist_player = Syft::Player::Agent; - Syft::LTLfSynthesizer synthesizer(symbolic_dfa, starting_player, - protagonist_player, symbolic_dfa.final_states(), - var_mgr->cudd_mgr()->bddOne()); - Syft::SynthesisResult result = synthesizer.run(); + for (size_t i = 0; i < formulas.size(); ++i){ + std::string name = (i == 0) ? " main_agent" : " peer" + std::to_string(i); + std::string prefix= out_dir + "/" + name; + std::cout << "Generating DFA for" << name << " with formula: " << formulas[i] << std::endl; + std::stringstream formula_stream(formulas[i]); + driver->parse(formula_stream); + auto ltlf_ptr = driver->get_result(); + + //Explicit DFA + auto ltlf_formula = std::dynamic_pointer_cast(ltlf_ptr); + Syft::ExplicitStateDfa dfa = Syft::ExplicitStateDfa::dfa_of_formula(*ltlf_formula); + dfa.export_dfa(prefix + ".mona"); + whitemech::lydia::print_mona_dfa( + dfa.dfa_, + prefix, + dfa.get_nb_variables() + ); + + //Explicit DFA + Syft::ExplicitStateDfaAdd explicit_dfa_add = Syft::ExplicitStateDfaAdd::from_dfa_mona(var_mgr, dfa); + explicit_dfa_add.dump_dot(prefix + "_add.dot"); + //Symbolic DFA + Syft::SymbolicStateDfa symbolic_dfa = Syft::SymbolicStateDfa::from_explicit(std::move(explicit_dfa_add)); + symbolic_dfa.dump_dot(prefix + "_symbolic.dot"); + + + } + std::cout << "\nCompleted" << std::endl; - std::cout << (result.realizability? "" : "NOT ") << "REALIZABLE" << std::endl; return 0; + } \ No newline at end of file diff --git a/examples/01_quickstart/quickstartB.cpp b/examples/01_quickstart/quickstartB.cpp new file mode 100644 index 00000000..402c4495 --- /dev/null +++ b/examples/01_quickstart/quickstartB.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include + +#include + +#include "automata/ExplicitStateDfa.h" +#include "automata/ExplicitStateDfaAdd.h" +#include "automata/SymbolicStateDfa.h" +#include "game/InputOutputPartition.h" +#include "Player.h" +#include "VarMgr.h" +#include "synthesizer/LTLfSynthesizer.h" + +int main(int argc, char ** argv) { + + // Define formulas + std::vector formula_strs = { + "F(a & b)", "G(c)", "F(d | e)", "G(f)", "F(g & h)", "G(i)" + }; + + std::vector input_vars = {"a", "b"}; + std::vector> agent_vars = { + {"c"}, {"d", "e"}, {"f"}, {"g", "h"}, {"i"} + }; + + // Parse the formulas + std::vector formulas; + auto driver = std::make_shared(); + for (const auto& formula_str : formula_strs) { + std::stringstream formula_stream(formula_str); + driver->parse(formula_stream); + formulas.push_back(std::dynamic_pointer_cast(driver->get_result())); + } + + // Initialize partition and variables + Syft::InputOutputPartition partition = Syft::InputOutputPartition::construct_from_input(input_vars, agent_vars); + std::shared_ptr var_mgr = std::make_shared(); + + std::vector all_named_vars = partition.input_variables; + for (const auto& ag : partition.agent_variables) { + all_named_vars.insert(all_named_vars.end(), ag.begin(), ag.end()); + } + var_mgr->create_named_variables(all_named_vars); + + for (std::size_t i = 0; i < partition.agent_variables.size(); ++i) { + var_mgr->create_agent_variables(i, partition.agent_variables[i]); + } + var_mgr->partition_variables(partition.input_variables, partition.agent_variables); + + std::string output_dir = "quickstart_outputs"; + std::filesystem::remove_all(output_dir); + std::filesystem::create_directories(output_dir); + + + // Build and Save DFAs + for (size_t i = 0; i < formulas.size(); ++i) { + std::string base_name; + if (i == 0) { + base_name = "environment"; + } else if (i == 1) { + base_name = "main_agent"; + } else { + base_name = "peer_agent_" + std::to_string(i - 1); + } + + // build the explicit-state DFA + Syft::ExplicitStateDfa explicit_dfa = Syft::ExplicitStateDfa::dfa_of_formula(*formulas[i]); + Syft::ExplicitStateDfaAdd explicit_dfa_add = Syft::ExplicitStateDfaAdd::from_dfa_mona(var_mgr, explicit_dfa); + + explicit_dfa.export_dfa(output_dir + "/" + base_name + ".mona"); + explicit_dfa_add.dump_dot(output_dir + "/" + base_name + "_explicit.dot"); + + // build the symbolic-state DFA from the explicit-state DFA + Syft::SymbolicStateDfa symbolic_dfa = Syft::SymbolicStateDfa::from_explicit(std::move(explicit_dfa_add)); + + + std::string symbolic_dot_path = output_dir + "/" + base_name + "_symbolic.dot"; + symbolic_dfa.dump_dot(symbolic_dot_path); + + //std::cout << "Saved symbolic DFA for " << base_name << " to " << symbolic_dot_path << std::endl; + } + + return 0; +} diff --git a/examples/01_quickstart/quickstartO.cpp b/examples/01_quickstart/quickstartO.cpp new file mode 100644 index 00000000..ef749880 --- /dev/null +++ b/examples/01_quickstart/quickstartO.cpp @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include + +#include + +#include "automata/ExplicitStateDfa.h" +#include "automata/ExplicitStateDfaAdd.h" +#include "automata/SymbolicStateDfa.h" +#include "game/InputOutputPartition.h" +#include "Player.h" +#include "VarMgr.h" +#include "synthesizer/LTLfSynthesizer.h" + + +int main(int argc, char ** argv) { + + // Define formulas: one for environment, one for main agent, and for peer agents + std::vector formula_strs = { + "F(a & b)", // Environment formula + "G(c)", // Main agent formula + "F(d | e)", // Peer agent 1 formula + "G(f)", // Peer agent 2 formula + "F(g & h)", // Peer agent 3 formula + "G(i)" // Peer agent 4 formula + }; + + // Define variables: inputs and agents (vector of vectors) + std::vector input_vars = {"a", "b"}; + std::vector> agent_vars = { + {"c"}, // Main agent (index 0) + {"d", "e"}, // Peer agent 1 (index 1) + {"f"}, // Peer agent 2 (index 2) + {"g", "h"}, // Peer agent 3 (index 3) + {"i"} // Peer agent 4 (index 4) + }; + + // parse the formula + std::vector formulas; + auto driver = std::make_shared(); + for (const auto& formula_str : formula_strs) { + std::stringstream formula_stream(formula_str); + driver->parse(formula_stream); + formulas.push_back(std::dynamic_pointer_cast(driver->get_result())); + } + + // Initialize partition and variables + Syft::InputOutputPartition partition = Syft::InputOutputPartition::construct_from_input(input_vars, agent_vars); + std::shared_ptr var_mgr = std::make_shared(); + + // Create all named variables + std::vector all_named_vars = partition.input_variables; + for (const auto& ag : partition.agent_variables) { + all_named_vars.insert(all_named_vars.end(), ag.begin(), ag.end()); + } + var_mgr->create_named_variables(all_named_vars); + + for (std::size_t i = 0; i < partition.agent_variables.size(); ++i) { + var_mgr->create_agent_variables(i, partition.agent_variables[i]); + } + var_mgr->partition_variables(partition.input_variables, partition.agent_variables); + + std::string output_dir = "quickstart_outputs"; + std::filesystem::remove_all(output_dir); + std::filesystem::create_directories(output_dir); + + // Build DFAs for each agent + std::vector explicit_dfas_add; + std::vector symbolic_dfas; + for (size_t i = 0; i < formulas.size(); ++i) { + Syft::ExplicitStateDfa explicit_dfa = Syft::ExplicitStateDfa::dfa_of_formula(*formulas[i]); + Syft::ExplicitStateDfaAdd explicit_dfa_add = Syft::ExplicitStateDfaAdd::from_dfa_mona(var_mgr, explicit_dfa); + std::cout << "DFA " << i << " (agent " << i << "): " << explicit_dfa_add.state_count() << " states" << std::endl; + + std::string mona_file = output_dir + "/agent_" + std::to_string(i) + ".mona"; + //std::cout << "Exporting DFA " << i << " to " << mona_file << std::endl; + explicit_dfa.export_dfa(mona_file); + + explicit_dfas_add.push_back(explicit_dfa_add); + + // Build symbolic DFA + Syft::SymbolicStateDfa symbolic_dfa = Syft::SymbolicStateDfa::from_explicit(std::move(explicit_dfa_add)); + symbolic_dfas.push_back(symbolic_dfa); + } + + // Check if everything worked + std::cout << "Multi-agent DFA construction successful! All modifications work." << std::endl; + + + + // do synthesis + //var_mgr->partition_variables(partition.input_variables, partition.output_variables); + //Syft::Player starting_player = Syft::Player::Agent; + //Syft::Player protagonist_player = Syft::Player::Agent; + //Syft::LTLfSynthesizer synthesizer(symbolic_dfa, starting_player, + // protagonist_player, symbolic_dfa.final_states(), + // var_mgr->cudd_mgr()->bddOne()); + //Syft::SynthesisResult result = synthesizer.run(); + + + //std::cout << (result.realizability? "" : "NOT ") << "REALIZABLE" << std::endl; + return 0; +} \ No newline at end of file diff --git a/examples/01_quickstart/quickstartTLSF.cpp b/examples/01_quickstart/quickstartTLSF.cpp new file mode 100644 index 00000000..8cc5d8c2 --- /dev/null +++ b/examples/01_quickstart/quickstartTLSF.cpp @@ -0,0 +1,87 @@ +#include +#include +#include "Parser.h" +#include "VarMgr.h" +#include +#include +#include +#include +#include +#include "lydia/mona_ext/mona_ext_base.hpp" +#include +#include "automata/ExplicitStateDfa.h" +#include "automata/ExplicitStateDfaAdd.h" +#include "automata/SymbolicStateDfa.h" +#include "game/InputOutputPartition.h" +#include "Player.h" +#include "synthesizer/LTLfSynthesizer.h" + +int main(int argc, char ** argv) { + auto parser = Syft::Parser::read_from_file("/LydiaSyft/syfco", "/examples/01_quickstart/test_multi.tlsf"); + auto input = parser.get_input_variables(); + auto agents = parser.get_agent_variables(); + auto formulas = parser.get_agent_formulas(); + std::cout << "Input variables found: " << input.size() << std::endl; + std::cout << "Number of agents found: " << agents.size() << std::endl; + std::cout << "Agent formulas found: " << formulas.size() << std::endl; + + std::cout << "Input variables: " << std::endl; + for (const auto& var : input) { + std::cout << var << std::endl; + } + for (std::size_t i = 0; i < agents.size(); ++i) { + std::cout << "Agent " << i << " variables: " << std::endl; + for (const auto& var : agents[i]) { + std::cout << var << std::endl; + } + std::cout << "Agent " << i << " formula: " << formulas[i] << std::endl; + } + + + std::shared_ptr var_mgr = std::make_shared(); + var_mgr->create_named_variables(parser.get_input_variables()); + for (const auto& agent_vars : parser.get_agent_variables()) { + var_mgr->create_named_variables(agent_vars); + } + var_mgr->partition_variables(parser.get_input_variables(), parser.get_agent_variables()); + + //Directory for showing results + std::string out_dir = "dfa_outputs"; + std::filesystem::remove_all(out_dir); + std::filesystem::create_directories(out_dir); + + auto driver = std::make_shared(); + + for (size_t i = 0; i < formulas.size(); ++i){ + std::string name = (i == 0) ? " main_agent" : " peer" + std::to_string(i); + std::string prefix= out_dir + "/" + name; + std::cout << "Generating DFA for" << name << " with formula: " << formulas[i] << std::endl; + std::stringstream formula_stream(formulas[i]); + driver->parse(formula_stream); + auto ltlf_ptr = driver->get_result(); + + //Explicit DFA + auto ltlf_formula = std::dynamic_pointer_cast(ltlf_ptr); + Syft::ExplicitStateDfa dfa = Syft::ExplicitStateDfa::dfa_of_formula(*ltlf_formula); + dfa.export_dfa(prefix + ".mona"); + whitemech::lydia::print_mona_dfa( + dfa.dfa_, + prefix, + dfa.get_nb_variables() + ); + + //Explicit DFA ADD + Syft::ExplicitStateDfaAdd explicit_dfa_add = Syft::ExplicitStateDfaAdd::from_dfa_mona(var_mgr, dfa); + explicit_dfa_add.dump_dot(prefix + "_add.dot"); + + //Symbolic DFA + Syft::SymbolicStateDfa symbolic_dfa = Syft::SymbolicStateDfa::from_explicit(std::move(explicit_dfa_add)); + symbolic_dfa.dump_dot(prefix + "_symbolic.dot"); + + + } + std::cout << "\nCompleted" << std::endl; + + return 0; + +} \ No newline at end of file diff --git a/examples/01_quickstart/test_multi.tlsf b/examples/01_quickstart/test_multi.tlsf new file mode 100644 index 00000000..cfef0d09 --- /dev/null +++ b/examples/01_quickstart/test_multi.tlsf @@ -0,0 +1,27 @@ +INFO { + TITLE: "Test parser" + DESCRIPTION: "test parser" + SEMANTICS: Finite,Moore + TARGET: Mealy +} + +MAIN { + + INPUTS { + a1; + a2; + } + + OUTPUTS { + b1; //Main + b2; //peer 1 + b3; b4; //peer 2 + } + + GUARANTEES { + G(a1 -> b1); + F(b2); + G(a2 -> F b3); + } + +} diff --git a/examples/01_quickstart/test_vars.part b/examples/01_quickstart/test_vars.part new file mode 100644 index 00000000..60e92208 --- /dev/null +++ b/examples/01_quickstart/test_vars.part @@ -0,0 +1,4 @@ +.inputs:x y +.agent0:a +.agent1:b +.agent2:c \ No newline at end of file diff --git a/examples/02_dfa_representation/dfa_representation.cpp b/examples/02_dfa_representation/dfa_representation.cpp index fdfd94ed..ac0b8b8a 100644 --- a/examples/02_dfa_representation/dfa_representation.cpp +++ b/examples/02_dfa_representation/dfa_representation.cpp @@ -2,60 +2,114 @@ #include #include #include +#include +#include #include #include "automata/ExplicitStateDfa.h" #include "automata/ExplicitStateDfaAdd.h" #include "automata/SymbolicStateDfa.h" +#include "game/InputOutputPartition.h" #include "VarMgr.h" #include "lydia/mona_ext/mona_ext_base.hpp" int main(int argc, char ** argv) { - // explicit DFA - std::string request = "request"; - std::string response = "response"; - std::string formula_str = "G(request -> F(response))"; - std::cout << "Input formula: " << formula_str << std::endl; + // Multi-agent formulas + std::vector formula_strs = { + "G(req -> F(grant))", // Environment: If request, eventually grant + "F(serve)", // Main agent: Eventually serve + "G(ready)", // Peer agent 1: Always ready + "F(complete)" // Peer agent 2: Eventually complete + }; + std::vector input_vars = {"req"}; + std::vector> agent_vars = { + {"grant"}, // Main agent + {"serve"}, // Peer 1 + {"ready"}, // Peer 2 + {"complete"} // Peer 3 + }; - // parse the formula + // Parse formulas + std::vector formulas; auto driver = std::make_shared(); - std::stringstream formula_stream(formula_str); - driver->parse(formula_stream); - whitemech::lydia::ltlf_ptr formula = driver->get_result(); + for (const auto& formula_str : formula_strs) { + std::cout << "Input formula: " << formula_str << std::endl; + std::stringstream formula_stream(formula_str); + driver->parse(formula_stream); + formulas.push_back(std::dynamic_pointer_cast(driver->get_result())); + } - // build the explicit-state DFA - Syft::ExplicitStateDfa dfa = Syft::ExplicitStateDfa::dfa_of_formula(*formula); + // Build explicit-state DFAs + std::vector dfas; + for (const auto& formula : formulas) { + Syft::ExplicitStateDfa dfa = Syft::ExplicitStateDfa::dfa_of_formula(*formula); + dfas.push_back(dfa); + } - std::cout << "Printing the DFA in textual form: " << std::endl; - dfa.dfa_print(); + // Initialize VarMgr for ADD representation + Syft::InputOutputPartition partition = Syft::InputOutputPartition::construct_from_input(input_vars, agent_vars); + std::shared_ptr var_mgr = std::make_shared(); + std::vector all_vars = input_vars; + for (const auto& ag : agent_vars) { + all_vars.insert(all_vars.end(), ag.begin(), ag.end()); + } + var_mgr->create_named_variables(all_vars); + for (size_t i = 0; i < agent_vars.size(); ++i) { + var_mgr->create_agent_variables(i, agent_vars[i]); + } + var_mgr->partition_variables(input_vars, agent_vars); - // export the DFA in MONA format - std::cout << "Printing the DFA in MONA format..." << std::endl; - dfa.export_dfa("main.mona"); + std::vector labels; + labels.push_back("Environment"); + labels.push_back("Main Agent"); + for (size_t i = 2; i < dfas.size(); ++i) { + labels.push_back("Peer Agent " + std::to_string(i - 1)); + } - // export the DFA in DOT and SVG formats: - std::cout << "Exporting the explicit-state MONA DFA in DOT and SVG to files 'main.dot' and 'main.svg'..." << std::endl; - whitemech::lydia::print_mona_dfa( - dfa.dfa_, - "main", - dfa.get_nb_variables() - ); + std::string output_dir = "dfa_representation_outputs"; + std::filesystem::remove_all(output_dir); + std::filesystem::create_directories(output_dir); - // transform in explicit state form with ADD - std::shared_ptr var_mgr = std::make_shared(); - var_mgr->create_named_variables({request, response}); - Syft::ExplicitStateDfaAdd explicit_dfa_add = Syft::ExplicitStateDfaAdd::from_dfa_mona(var_mgr, dfa); - std::cout << "Number of states: " << explicit_dfa_add.state_count() << std::endl; - std::cout << "Exporting the explicit-state ADD DFA in DOT format to file 'main_add.dot'..." << std::endl; - explicit_dfa_add.dump_dot("main_add.dot"); - - // build the symbolic-state DFA from the explicit-state DFA - Syft::SymbolicStateDfa symbolic_dfa = Syft::SymbolicStateDfa::from_explicit(std::move(explicit_dfa_add)); - std::cout << "Exporting the symbolic-state DFA in DOT format to file 'main_symbolic.dot'..." << std::endl; - symbolic_dfa.dump_dot("main_symbolic.dot"); + // Show representations for each DFA + for (size_t i = 0; i < dfas.size(); ++i) { + std::string file_prefix = labels[i]; + std::replace(file_prefix.begin(), file_prefix.end(), ' ', '_'); + std::cout << "\n--- DFA " << i << " (" << labels[i] << ") ---" << std::endl; + std::cout << "Printing the DFA in textual form: " << std::endl; + dfas[i].dfa_print(); + + // Export in MONA format + std::string mona_file = output_dir + "/" + file_prefix + ".mona"; + //std::cout << "Printing the DFA in MONA format to " << mona_file << "..." << std::endl; + dfas[i].export_dfa(mona_file); + + // Export in DOT and SVG + std::string dot_base = output_dir + "/" + file_prefix; + //std::cout << "Exporting the explicit-state MONA DFA in DOT and SVG to '" << dot_base << ".dot' and '" << dot_base << ".svg'..." << std::endl; + whitemech::lydia::print_mona_dfa( + dfas[i].dfa_, + dot_base, + dfas[i].get_nb_variables() + ); + + // Transform to explicit state form with ADD + Syft::ExplicitStateDfaAdd explicit_dfa_add = Syft::ExplicitStateDfaAdd::from_dfa_mona(var_mgr, dfas[i]); + std::cout << "Number of states: " << explicit_dfa_add.state_count() << std::endl; + std::string add_dot = output_dir + "/" + file_prefix + "_add.dot"; + //std::cout << "Exporting the explicit-state ADD DFA in DOT format to file '" << add_dot << "'..." << std::endl; + explicit_dfa_add.dump_dot(add_dot); + + // Build symbolic-state DFA + Syft::SymbolicStateDfa symbolic_dfa = Syft::SymbolicStateDfa::from_explicit(std::move(explicit_dfa_add)); + std::string sym_dot = output_dir + "/" + file_prefix + "_symbolic.dot"; + //std::cout << "Exporting the symbolic-state DFA in DOT format to file '" << sym_dot << "'..." << std::endl; + symbolic_dfa.dump_dot(sym_dot); + } + + std::cout << "\nMulti-agent DFA representation example completed successfully!" << std::endl; return 0; } \ No newline at end of file diff --git a/examples/03_dfa_creation_and_manipulation/dfa_creation_and_manipulation.cpp b/examples/03_dfa_creation_and_manipulation/dfa_creation_and_manipulation.cpp index 4737582b..c2de4856 100644 --- a/examples/03_dfa_creation_and_manipulation/dfa_creation_and_manipulation.cpp +++ b/examples/03_dfa_creation_and_manipulation/dfa_creation_and_manipulation.cpp @@ -15,15 +15,15 @@ int main(int argc, char ** argv) { auto driver = std::make_shared(); std::stringstream formula_p_stream(formula_p_str); driver->parse(formula_p_stream); - whitemech::lydia::ltlf_ptr formula_p = driver->get_result(); + whitemech::lydia::ltlf_ptr formula_p = std::dynamic_pointer_cast(driver->get_result()); std::stringstream formula_q_stream(formula_q_str); driver->parse(formula_q_stream); - whitemech::lydia::ltlf_ptr formula_q = driver->get_result(); + whitemech::lydia::ltlf_ptr formula_q = std::dynamic_pointer_cast(driver->get_result()); std::stringstream formula_r_stream(formula_r_str); driver->parse(formula_r_stream); - whitemech::lydia::ltlf_ptr formula_r = driver->get_result(); + whitemech::lydia::ltlf_ptr formula_r = std::dynamic_pointer_cast(driver->get_result()); // build the explicit-state DFAs Syft::ExplicitStateDfa dfa_p = Syft::ExplicitStateDfa::dfa_of_formula(*formula_p); diff --git a/examples/09_reachability/CMakeLists.txt b/examples/09_reachability/CMakeLists.txt new file mode 100644 index 00000000..cd64692f --- /dev/null +++ b/examples/09_reachability/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(reachability_example reachability.cpp) + +target_include_directories(reachability_example PRIVATE ${UTILS_INCLUDE_PATH} ${PARSER_INCLUDE_PATH} ${SYNTHESIS_INCLUDE_PATH} ${EXT_INCLUDE_PATH}) +target_link_libraries(reachability_example ${PARSER_LIB_NAME} ${SYNTHESIS_LIB_NAME} ${UTILS_LIB_NAME} ${LYDIA_LIBRARIES}) + diff --git a/examples/09_reachability/reachability.cpp b/examples/09_reachability/reachability.cpp new file mode 100644 index 00000000..92f1e05c --- /dev/null +++ b/examples/09_reachability/reachability.cpp @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "automata/ExplicitStateDfa.h" +#include "automata/ExplicitStateDfaAdd.h" +#include "automata/SymbolicStateDfa.h" +#include "game/InputOutputPartition.h" + +#include "Actor.h" +#include "VarMgr.h" +#include "game/Reachability_multiagent.hpp" +#include "game/DfaGameSynthesizer_multiagent.h" +#include "Utils.h" + +using namespace Syft; + +int main(int argc, char ** argv) { + // 1. Command line arguments check + if (argc < 2) { + std::cerr << "[ERROR] You must specify the test case directory.\n"; + std::cerr << "Usage: " << argv[0] << " \n"; + return 1; + } + try { + // Clean path handling using std::filesystem + std::filesystem::path root_dir = argv[1]; + std::filesystem::path part_file = root_dir / "var.part"; + + if (!std::filesystem::exists(part_file)) { + throw std::runtime_error("Partition file not found: " + part_file.string()); + } + // 2. Read partition + InputOutputPartition partition = InputOutputPartition::read_from_file(part_file.string()); + auto input_vars = partition.input_variables; + auto agent_vars_groups = partition.agent_variables; + size_t num_agents = agent_vars_groups.size(); + + std::cout << "Input variables: " << input_vars.size() << std::endl; + for (const auto& var : input_vars) std::cout << " - " << var << std::endl; + + for (std::size_t i = 0; i < num_agents; ++i) { + std::cout << "Agent " << i << " variables: " << std::endl; + for (const auto& var : agent_vars_groups[i]) std::cout << " - " << var << std::endl; + } + + std::vector actors; + actors.push_back(Actor::Environment()); + for (size_t i = 0; i < num_agents; ++i) { + actors.push_back((i == 0) ? Actor::MainAgent() : Actor::PeerAgent(i)); + } + + std::vector formula_files = { root_dir / "env.ltlf" }; + for (size_t i = 0; i < num_agents; ++i) { + formula_files.push_back(root_dir / ("agent" + std::to_string(i) + ".ltlf")); + } + + auto var_mgr = std::make_shared(); + var_mgr->create_named_variables(input_vars); + for (const auto& vars : agent_vars_groups) { + var_mgr->create_named_variables(vars); + } + var_mgr->partition_variables(input_vars, agent_vars_groups); + + // 5. Formula parsing and DFA construction (Executed ONCE!) + std::vector dfas; + + for (const auto& filepath : formula_files) { + if (!std::filesystem::exists(filepath)) { + throw std::runtime_error("Missing formula file: " + filepath.string()); + } + + std::ifstream file(filepath); + std::stringstream buffer; + buffer << file.rdbuf(); + std::stringstream formula_stream(buffer.str()); + + // New driver for each file to avoid internal parser state conflicts + auto driver = std::make_shared(); + driver->parse(formula_stream); + + auto ltlf_formula = std::dynamic_pointer_cast(driver->get_result()); + if (!ltlf_formula) { + throw std::runtime_error("Error casting formula for: " + filepath.string()); + } + + Syft::SymbolicStateDfa symbolic_dfa = Syft::do_dfa_construction(*ltlf_formula, var_mgr); + dfas.push_back(std::move(symbolic_dfa)); + } + + // 6. Build the Global Arena (OR product of DFAs) + SymbolicStateDfa arena = SymbolicStateDfa::product_OR(dfas); + + // Initial actor setup + Actor starting_actor = Actor::MainAgent(); + + // 7. Protagonist loop: Solving the game for each actor + std::cout << "\n=== MULTI-AGENT GAME SOLVING ===" << std::endl; + for (size_t i = 0; i < actors.size(); ++i) { + const Actor& protagonist_actor = actors[i]; + + // Find the correct DFA index associated with the protagonist's goal + size_t actor_dfa_index = protagonist_actor.is_environment() ? 0 : 1 + protagonist_actor.id(); + + CUDD::BDD goal_v = dfas[actor_dfa_index].final_states(); + CUDD::BDD space = var_mgr->cudd_mgr()->bddOne(); + + std::cout << "--------------------------------------------" << std::endl; + std::cout << "Starting actor: " << (starting_actor.is_environment() ? "ENV" : "Agent " + std::to_string(starting_actor.id())) << std::endl; + std::cout << "Protagonist actor: " << (protagonist_actor.is_environment() ? "ENV" : "Agent " + std::to_string(protagonist_actor.id())) << std::endl; + + // Synthesis execution for the current protagonist + Reachability_multiagent game(arena, starting_actor, protagonist_actor, goal_v, space, num_agents); + SynthesisResult result = game.run(); + + if (result.realizability) { + std::cout << "Result: REALIZABLE (TRUE)" << std::endl; + if (result.transducer_multiagent != nullptr) { + std::string out_name = "reach_strategy_" + + (protagonist_actor.is_environment() ? "env" : "agent" + std::to_string(protagonist_actor.id())) + ".dot"; + result.transducer_multiagent->dump_dot(out_name); + std::cout << " -> Winning strategy saved to: " << out_name << std::endl; + } + } else { + std::cout << "Result: UNREALIZABLE (FALSE)" << std::endl; + } + } + + } catch (const std::exception& e) { + std::cerr << "[FATAL ERROR] " << e.what() << std::endl; + return 1; + } + return 0; +} \ No newline at end of file diff --git a/examples/10_buchi_reachability/CMakeLists.txt b/examples/10_buchi_reachability/CMakeLists.txt new file mode 100644 index 00000000..590b3fc3 --- /dev/null +++ b/examples/10_buchi_reachability/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(buchi_reachability_example buchi_reachability.cpp) + +target_include_directories(buchi_reachability_example PRIVATE ${UTILS_INCLUDE_PATH} ${PARSER_INCLUDE_PATH} ${SYNTHESIS_INCLUDE_PATH} ${EXT_INCLUDE_PATH}) +target_link_libraries(buchi_reachability_example ${PARSER_LIB_NAME} ${SYNTHESIS_LIB_NAME} ${UTILS_LIB_NAME} ${LYDIA_LIBRARIES}) + diff --git a/examples/10_buchi_reachability/buchi_reachability.cpp b/examples/10_buchi_reachability/buchi_reachability.cpp new file mode 100644 index 00000000..e8f0dcb4 --- /dev/null +++ b/examples/10_buchi_reachability/buchi_reachability.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include +#include +#include "lydia/mona_ext/mona_ext_base.hpp" +#include +#include "automata/ExplicitStateDfa.h" +#include "automata/ExplicitStateDfaAdd.h" +#include "automata/SymbolicStateDfa.h" +#include "game/InputOutputPartition.h" + +#include "Actor.h" +#include "VarMgr.h" +#include "game/BuchiReachability_multiagent.hpp" +#include "game/DfaGameSynthesizer_multiagent.h" + +using namespace Syft; + +int main(int argc, char ** argv) { + // 1. Command line arguments check + if (argc < 2) { + std::cerr << "[ERROR] You must specify the test case directory.\n"; + std::cerr << "Usage: " << argv[0] << " \n"; + return 1; + } + + try { + // Clean path handling using std::filesystem + std::filesystem::path root_dir = argv[1]; + std::filesystem::path part_file = root_dir / "var.part"; + + if (!std::filesystem::exists(part_file)) { + throw std::runtime_error("Partition file not found: " + part_file.string()); + } + + // 2. Read partition + InputOutputPartition partition = InputOutputPartition::read_from_file(part_file.string()); + auto input_vars = partition.input_variables; + auto agent_vars_groups = partition.agent_variables; + size_t num_agents = agent_vars_groups.size(); + + // Initial informative prints + std::cout << "Input variables: " << input_vars.size() << std::endl; + for (const auto& var : input_vars) std::cout << " - " << var << std::endl; + + for (std::size_t i = 0; i < num_agents; ++i) { + std::cout << "Agent " << i << " variables: " << std::endl; + for (const auto& var : agent_vars_groups[i]) std::cout << " - " << var << std::endl; + } + + // 3. Initialize Variable Manager + auto var_mgr = std::make_shared(); + var_mgr->create_named_variables(input_vars); + for (const auto& vars : agent_vars_groups) { + var_mgr->create_named_variables(vars); + } + var_mgr->partition_variables(input_vars, agent_vars_groups); + + // 4. Automatic Actor and Formula files generation + std::vector actors; + actors.push_back(Actor::Environment()); + for (size_t i = 0; i < num_agents; ++i) { + actors.push_back((i == 0) ? Actor::MainAgent() : Actor::PeerAgent(i)); + } + + std::vector formula_files = { root_dir / "env.ltlf" }; + for (size_t i = 0; i < num_agents; ++i) { + formula_files.push_back(root_dir / ("agent" + std::to_string(i) + ".ltlf")); + } + + // 5. Formula parsing and DFA construction (Symbolic & Explicit via Mona) + std::vector dfas; + std::vector explicit_dfas; + + auto driver = std::make_shared(); + + for (const auto& filepath : formula_files) { + if (!std::filesystem::exists(filepath)) { + throw std::runtime_error("Missing formula file: " + filepath.string()); + } + + std::ifstream file(filepath); + std::stringstream buffer; + buffer << file.rdbuf(); + std::stringstream formula_stream(buffer.str()); + + driver->parse(formula_stream); + + auto ltlf_formula = std::dynamic_pointer_cast(driver->get_result()); + if (!ltlf_formula) { + throw std::runtime_error("Error casting formula for: " + filepath.string()); + } + + // Generate explicit MONA DFA + ExplicitStateDfa dfa_mona = ExplicitStateDfa::dfa_of_formula(*ltlf_formula); + + // Convert to Symbolic State DFA + ExplicitStateDfaAdd explicit_add = ExplicitStateDfaAdd::from_dfa_mona(var_mgr, dfa_mona); + SymbolicStateDfa symbolic_dfa = SymbolicStateDfa::from_explicit(std::move(explicit_add)); + + dfas.push_back(std::move(symbolic_dfa)); + explicit_dfas.push_back(std::move(dfa_mona)); + } + + // 6. Build Arena (OR product of DFAs) + SymbolicStateDfa arena = SymbolicStateDfa::product_OR(dfas); + ExplicitStateDfa arena_explicit = ExplicitStateDfa::dfa_product_or(explicit_dfas); + + // Initial actor setup + Actor starting_actor = Actor::MainAgent(); + + // 7. Protagonist loop: Solving the game for each actor + std::cout << "\n=== MULTI-AGENT BUCHI REACHABILITY SOLVING ===" << std::endl; + for (size_t i = 0; i < actors.size(); ++i) { + const Actor& protagonist_actor = actors[i]; + + // Goal is based on protagonist's formula only + size_t actor_dfa_index = protagonist_actor.is_environment() ? 0 : 1 + protagonist_actor.id(); + + CUDD::BDD buchi_condition = dfas[actor_dfa_index].final_states(); + CUDD::BDD space = var_mgr->cudd_mgr()->bddOne(); + + std::cout << "--------------------------------------------" << std::endl; + std::cout << "Starting actor: " << (starting_actor.is_environment() ? "ENV" : "Agent " + std::to_string(starting_actor.id())) << std::endl; + std::cout << "Protagonist actor: " << (protagonist_actor.is_environment() ? "ENV" : "Agent " + std::to_string(protagonist_actor.id())) << std::endl; + + // Synthesis execution for Buchi Game + BuchiReachability_multiagent game(arena, starting_actor, protagonist_actor, buchi_condition, space, num_agents); + SynthesisResult result = game.run(); + + if (result.realizability) { + std::cout << "Result: REALIZABLE (TRUE)" << std::endl; + if (result.transducer_multiagent != nullptr) { + std::string out_name = "winning_strategy_" + + (protagonist_actor.is_environment() ? "env" : "agent" + std::to_string(protagonist_actor.id())) + ".dot"; + result.transducer_multiagent->dump_dot(out_name); + std::cout << " -> Winning strategy saved to: " << out_name << std::endl; + } + } else { + std::cout << "Result: UNREALIZABLE (FALSE)" << std::endl; + } + } + + } catch (const std::exception& e) { + std::cerr << "\n[FATAL ERROR] " << e.what() << std::endl; + return 1; + } + return 0; +} \ No newline at end of file diff --git a/examples/11_obligation/CMakeLists.txt b/examples/11_obligation/CMakeLists.txt new file mode 100644 index 00000000..bcd6c35f --- /dev/null +++ b/examples/11_obligation/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(obligation_example obligation.cpp) + +target_include_directories(obligation_example PRIVATE ${UTILS_INCLUDE_PATH} ${PARSER_INCLUDE_PATH} ${SYNTHESIS_INCLUDE_PATH} ${EXT_INCLUDE_PATH}) +target_link_libraries(obligation_example ${PARSER_LIB_NAME} ${SYNTHESIS_LIB_NAME} ${UTILS_LIB_NAME} ${LYDIA_LIBRARIES}) + diff --git a/examples/11_obligation/obligation.cpp b/examples/11_obligation/obligation.cpp new file mode 100644 index 00000000..d23f2672 --- /dev/null +++ b/examples/11_obligation/obligation.cpp @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include +#include "lydia/mona_ext/mona_ext_base.hpp" +#include +#include "Parser.h" +#include + +#include "automata/ExplicitStateDfa.h" +#include "automata/ExplicitStateDfaAdd.h" +#include "automata/SymbolicStateDfa.h" +#include "game/InputOutputPartition.h" +#include "synthesizer/ObligationLTLfPlusSynthesizer.h" +#include "Actor.h" +#include "game/BuchiReachability_multiagent.hpp" +//#include "/home/stella/LydiaSyft/src/synthesis/source/synthesizer/ObligationLTLfPlusSynthesizer.cpp" + +#include "Actor.h" +#include "VarMgr.h" +#include "Utils.h" +#include "Synthesizer.h" + +using namespace Syft; +using namespace whitemech::lydia; + + +int main(int argc, char ** argv) { + // 1. Command line arguments check + if (argc < 2) { + std::cerr << "[ERROR] You must specify the test case directory.\n"; + std::cerr << "Usage: " << argv[0] << " \n"; + return 1; + } + + try { + // Clean path handling using std::filesystem + std::filesystem::path root_dir = argv[1]; + std::filesystem::path part_file = root_dir / "var.part"; + + if (!std::filesystem::exists(part_file)) { + throw std::runtime_error("Partition file not found: " + part_file.string()); + } + + InputOutputPartition partition = InputOutputPartition::read_from_file(part_file.string()); + + auto input_vars = partition.input_variables; + auto agent_vars_groups = partition.agent_variables; + size_t num_agents = agent_vars_groups.size(); + + auto var_mgr = std::make_shared(); + var_mgr->create_named_variables(input_vars); + for (const auto& vars : agent_vars_groups) { + var_mgr->create_named_variables(vars); + } + var_mgr->partition_variables(input_vars, agent_vars_groups); + + std::cout << "Input variables: " << std::endl; + for (const auto& var : input_vars) { + std::cout << var << std::endl; + } + for (std::size_t i = 0; i < agent_vars_groups.size(); ++i) { + std::cout << "Agent " << i << " variables: " << std::endl; + for (const auto& var : agent_vars_groups[i]) { + std::cout << var << std::endl; + } + } + + std::vector actors; + actors.push_back(Actor::Environment()); + for (size_t i = 0; i < num_agents; ++i) { + actors.push_back((i == 0) ? Actor::MainAgent() : Actor::PeerAgent(i)); + } + + //Formulas + std::vector formula_files = { root_dir / "env.ltlf" }; + for (size_t i = 0; i < num_agents; ++i) { + formula_files.push_back(root_dir / ("agent" + std::to_string(i) + ".ltlf")); + } + + // LTLf+ driver + std::shared_ptr driver = + std::make_shared(); + + std::vector dfas; + std::vector explicit_dfas; + std::size_t i = 0; + for (const auto& filepath : formula_files) { + if (!std::filesystem::exists(filepath)) { + throw std::runtime_error("Missing formula file: " + filepath.string()); + } + + std::ifstream file(filepath); + std::stringstream buffer; buffer << file.rdbuf(); + std::stringstream formula_stream(buffer.str()); + driver->parse(formula_stream); + auto result = driver->get_result(); + + // cast ast_ptr into ltlf_plus_ptr. Necessary since AbstractDriver is not template anymore + auto ptr_ltlf_plus_formula = + std::static_pointer_cast(result); + + // transform formula in PNF + auto pnf = whitemech::lydia::get_pnf_result(*ptr_ltlf_plus_formula); + Syft::LTLfPlus ltlf_plus_formula; + ltlf_plus_formula.color_formula_ = pnf.color_formula_; + ltlf_plus_formula.formula_to_color_= pnf.subformula_to_color_; + ltlf_plus_formula.formula_to_quantification_= pnf.subformula_to_quantifier_; + const auto& current_actor = actors[i]; + ObligationLTLfPlusSynthesizer synthesizer( + ltlf_plus_formula, + partition, + Actor::MainAgent(), // starting actor + current_actor, // protagonist actor + var_mgr + ); + + //we have to convert the ltlf plus formula into a dwa through function convert_to_symbolic + auto [final_dfa, color_to_final_states] = synthesizer.convert_to_symbolic_dfa(); + dfas.push_back(std::move(final_dfa)); + + ++i; + } + + // Build Arena + SymbolicStateDfa arena = SymbolicStateDfa::product_OR(dfas); + //save in a dot file the arena + /* std::filesystem::path arena_out = root_dir / "arena.dot"; + arena.dump_dot(arena_out.string()); + std::cout << "Arena saved to: " << arena_out.string() << std::endl; + */ + /* for (size_t i = 0; i < actors.size(); ++i) { + + + // Goal is based on protagonist's formula only + size_t actor_dfa_index; + if (actors[i].is_environment()) { + actor_dfa_index = 0; // ENV formula + } else { + actor_dfa_index = 1 + actors[i].id(); // Agent i formula + } + + CUDD::BDD buchi_condition = dfas[actor_dfa_index].final_states(); + //std::cout << "Buchi condition for " << (actors[actor_dfa_index].is_environment() ? "ENV" : "Agent " + std::to_string(actors[actor_dfa_index].id())) << ": " << buchi_condition << std::endl; + CUDD::BDD space = var_mgr->cudd_mgr()->bddOne(); + + + const Actor& protagonist_actor = actors[i]; + //we can change the starting actor: now is the MainAgent(), but we can set as starting one also Enviroment() or PeerAgent(id) + Actor starting_actor = Actor::MainAgent(); + + std::cout << "\n--------------------------------------------" << std::endl; + std::cout << "Starting actor: " << (starting_actor.is_environment() ? "ENV" : "Agent " + std::to_string(starting_actor.id())) << std::endl; + std::cout << "Protagonist actor: " << (protagonist_actor.is_environment() ? "ENV" : "Agent " + std::to_string(protagonist_actor.id())) << std::endl; + + BuchiReachability_multiagent game(arena, starting_actor, protagonist_actor, buchi_condition, space, num_agents); + SynthesisResult result = game.run(); + + if (result.realizability) { + std::cout << "Result: TRUE " << std::endl; + if(result.transducer_multiagent != nullptr){ + std::string filename = "winning_strategy_" + (protagonist_actor.is_environment() ? "env" : "agent" + std::to_string(protagonist_actor.id())) + ".dot"; + result.transducer_multiagent->dump_dot(filename); + std::cout << "Winning strategy saved to " << filename << std::endl; + } + } else { + std::cout << "Result: FALSE " << std::endl; + } + } */ + + } catch (const std::exception& e) { + std::cerr << "\n[FATAL ERROR] " << e.what() << std::endl; + return 1; + } + return 0; +} \ No newline at end of file diff --git a/examples/12_algorithm/CMakeLists.txt b/examples/12_algorithm/CMakeLists.txt new file mode 100644 index 00000000..12f0a2e4 --- /dev/null +++ b/examples/12_algorithm/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(algorithm_example algorithm.cpp) + +target_include_directories(algorithm_example PRIVATE ${UTILS_INCLUDE_PATH} ${PARSER_INCLUDE_PATH} ${SYNTHESIS_INCLUDE_PATH} ${EXT_INCLUDE_PATH}) +target_link_libraries(algorithm_example ${PARSER_LIB_NAME} ${SYNTHESIS_LIB_NAME} ${UTILS_LIB_NAME} ${LYDIA_LIBRARIES}) + diff --git a/examples/12_algorithm/algorithm.cpp b/examples/12_algorithm/algorithm.cpp new file mode 100644 index 00000000..1aef342f --- /dev/null +++ b/examples/12_algorithm/algorithm.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include +#include +#include +#include "lydia/mona_ext/mona_ext_base.hpp" +#include +#include "Parser.h" +#include + +#include "automata/ExplicitStateDfa.h" +#include "automata/ExplicitStateDfaAdd.h" +#include "automata/SymbolicStateDfa.h" +#include "game/InputOutputPartition.h" +#include "synthesizer/ObligationLTLfPlusSynthesizer.h" +#include "Actor.h" +#include "game/BuchiReachability_multiagent.hpp" + +#include "Actor.h" +#include "VarMgr.h" +#include "Utils.h" +#include "Synthesizer.h" + + +using namespace Syft; +using namespace whitemech::lydia; + +int main(int argc, char ** argv) { + if (argc < 2) { + std::cerr << "[ERROR] You must specify the test case directory.\n"; + std::cerr << "Usage: " << argv[0] << " \n"; + return 1; + } + try { + std::filesystem::path root_dir = argv[1]; + std::filesystem::path part_file = root_dir / "var.part"; + + if (!std::filesystem::exists(part_file)) { + throw std::runtime_error("Partition file not found: " + part_file.string()); + } + + InputOutputPartition partition = InputOutputPartition::read_from_file(part_file.string()); + + auto input_vars = partition.input_variables; + auto agent_vars_groups = partition.agent_variables; + size_t num_agents = agent_vars_groups.size(); + + auto var_mgr = std::make_shared(); + var_mgr->create_named_variables(input_vars); + for (const auto& vars : agent_vars_groups) { + var_mgr->create_named_variables(vars); + } + var_mgr->partition_variables(input_vars, agent_vars_groups); + + std::cout << "Input variables: " << std::endl; + for (const auto& var : input_vars) { + std::cout << var << std::endl; + } + for (std::size_t i = 0; i < agent_vars_groups.size(); ++i) { + std::cout << "Agent " << i << " variables: " << std::endl; + for (const auto& var : agent_vars_groups[i]) { + std::cout << var << std::endl; + } + } + + std::vector actors; + actors.push_back(Actor::Environment()); + for (size_t i = 0; i < num_agents; ++i) { + actors.push_back((i == 0) ? Actor::MainAgent() : Actor::PeerAgent(i)); + } + + //Formulas + std::vector formula_files = { root_dir / "env.ltlf" }; + for (size_t i = 0; i < num_agents; ++i) { + formula_files.push_back(root_dir / ("agent" + std::to_string(i) + ".ltlf")); + } + + // LTLf+ driver + std::shared_ptr driver = + std::make_shared(); + + + //Step 1) For each actor, construct the DWA of its safety/guarantuee specification + std::vector dwas; + std::size_t i = 0; + for (const auto& filename : formula_files) { + std::ifstream file(filename); + if (!file.is_open()) throw std::runtime_error("Impossible to open file " + filename.string()); + std::stringstream buffer; buffer << file.rdbuf(); + std::stringstream formula_stream(buffer.str()); + driver->parse(formula_stream); + auto result = driver->get_result(); + + // cast ast_ptr into ltlf_plus_ptr. Necessary since AbstractDriver is not template anymore + auto ptr_ltlf_plus_formula = + std::static_pointer_cast(result); + + // transform formula in PNF + auto pnf = whitemech::lydia::get_pnf_result(*ptr_ltlf_plus_formula); + Syft::LTLfPlus ltlf_plus_formula; + ltlf_plus_formula.color_formula_ = pnf.color_formula_; + ltlf_plus_formula.formula_to_color_= pnf.subformula_to_color_; + ltlf_plus_formula.formula_to_quantification_= pnf.subformula_to_quantifier_; + const auto& current_actor = actors[i]; + ObligationLTLfPlusSynthesizer synthesizer( + ltlf_plus_formula, + partition, + Actor::MainAgent(), // starting actor + current_actor, // protagonist actor + var_mgr + ); + + //we have to convert the ltlf plus formula into a symbolic dwa through function convert_to_symbolic + auto [final_dwa, color_to_final_states] = synthesizer.convert_to_symbolic_dfa(); + dwas.push_back(std::move(final_dwa)); + + ++i; + } + + //Step 2) From the environment's automata, construct the dwa that accepts CORE_env(phi_env) + + SymbolicStateDfa env_ne = SymbolicStateDfa::get_NE(dwas[0], actors[0]); + SymbolicStateDfa R_env = SymbolicStateDfa::get_CORE(env_ne, actors[0], Actor::MainAgent()); + SymbolicStateDfa R_env_comp = SymbolicStateDfa::complement(R_env); + //Step 3) For each peer agent w + // step 3.1) costruct a new automaton obtained as follows: comp(R_env) U dwa_w + // step 3.2) construct a new automaton that accepts CORE_w( CORE_env(phi_env) -> phi_w) + + std::vector R_peers; + std::vector R_peers_comp; + for (size_t i = 2; i < actors.size() ; ++i){ + std::vector vector; + vector.push_back(dwas[i]); + vector.push_back(R_env_comp); + SymbolicStateDfa dwa_prime = SymbolicStateDfa::product_OR(vector); + SymbolicStateDfa dwa_prime_ne = SymbolicStateDfa::get_NE(dwa_prime, actors[i]); + SymbolicStateDfa R_peer = SymbolicStateDfa::get_CORE(dwa_prime_ne, actors[i], Actor::MainAgent()); R_peers.push_back(R_peer); + R_peers.push_back(R_peer); + + SymbolicStateDfa R_peer_comp = SymbolicStateDfa::complement(R_peer); + R_peers_comp.push_back(R_peer_comp); + } + + //Step 4) Costruct a new dwa for the main agent obtained as follows: R_env_comp U R_peers_comp U dwa_0 + std::vector vector; + vector.push_back(R_env_comp); + vector.insert( + vector.end(), + R_peers_comp.begin(), + R_peers_comp.end() + ); + vector.push_back(dwas[1]); + SymbolicStateDfa product_arena = SymbolicStateDfa::product_OR(vector); + SymbolicStateDfa arena = SymbolicStateDfa::get_NE(product_arena, Actor::MainAgent()); + //var_mgr->print_mgr(); + + CUDD::BDD buchi_condition = arena.final_states(); + CUDD::BDD space = var_mgr->cudd_mgr()->bddOne(); + + //protagonist actor is Main Agent + Actor protagonist_actor = Actor::MainAgent(); + //we can change the starting actor: now is the MainAgent(), but we can set as starting one also Enviroment() or PeerAgent(id) + Actor starting_actor = Actor::MainAgent(); + + + std::cout << "Protagonist actor: " << (protagonist_actor.is_environment() ? "ENV" : "Agent " + std::to_string(protagonist_actor.id())) << std::endl; + + BuchiReachability_multiagent game(arena, starting_actor, protagonist_actor, buchi_condition, space, num_agents); + SynthesisResult result = game.run(); + + if (result.realizability) { + std::cout << "Result: TRUE " << std::endl; + if(result.transducer_multiagent != nullptr){ + std::string filename = "winning_strategy_" + (protagonist_actor.is_environment() ? "env" : "agent" + std::to_string(protagonist_actor.id())) + ".dot"; + result.transducer_multiagent->dump_dot(filename); + std::cout << "Winning strategy saved to " << filename << std::endl; + } + } else { + std::cout << "Result: FALSE " << std::endl; + } + + } catch (const std::exception& e) { + std::cerr << "[FATAL ERROR] " << e.what() << std::endl; + return 1; + } + return 0; +} \ No newline at end of file diff --git a/examples/12_algorithm_interactive/CMakeLists.txt b/examples/12_algorithm_interactive/CMakeLists.txt new file mode 100644 index 00000000..3a0b6309 --- /dev/null +++ b/examples/12_algorithm_interactive/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(algorithm_interactive_example algorithm_interactive.cpp) + +target_include_directories(algorithm_interactive_example PRIVATE ${UTILS_INCLUDE_PATH} ${PARSER_INCLUDE_PATH} ${SYNTHESIS_INCLUDE_PATH} ${EXT_INCLUDE_PATH}) +target_link_libraries(algorithm_interactive_example ${PARSER_LIB_NAME} ${SYNTHESIS_LIB_NAME} ${UTILS_LIB_NAME} ${LYDIA_LIBRARIES}) + diff --git a/examples/12_algorithm_interactive/algorithm_interactive.cpp b/examples/12_algorithm_interactive/algorithm_interactive.cpp new file mode 100644 index 00000000..75e5ed16 --- /dev/null +++ b/examples/12_algorithm_interactive/algorithm_interactive.cpp @@ -0,0 +1,355 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "lydia/mona_ext/mona_ext_base.hpp" +#include +#include "Parser.h" +#include + +#include "automata/ExplicitStateDfa.h" +#include "automata/ExplicitStateDfaAdd.h" +#include "automata/SymbolicStateDfa.h" +#include "game/InputOutputPartition.h" +#include "synthesizer/ObligationLTLfPlusSynthesizer.h" +#include "Actor.h" +#include "game/BuchiReachability_multiagent.hpp" + +#include "Actor.h" +#include "VarMgr.h" +#include "Utils.h" +#include "Synthesizer.h" + + +using namespace Syft; +using namespace whitemech::lydia; + +int main(int argc, char ** argv) { + if (argc < 2) { + std::cerr << "[ERROR] You must specify the test case directory.\n"; + std::cerr << "Usage: " << argv[0] << " \n"; + return 1; + } + try { + std::filesystem::path root_dir = argv[1]; + std::filesystem::path part_file = root_dir / "var.part"; + + if (!std::filesystem::exists(part_file)) { + throw std::runtime_error("Partition file not found: " + part_file.string()); + } + + InputOutputPartition partition = InputOutputPartition::read_from_file(part_file.string()); + + auto input_vars = partition.input_variables; + auto agent_vars_groups = partition.agent_variables; + size_t num_agents = agent_vars_groups.size(); + + auto var_mgr = std::make_shared(); + var_mgr->create_named_variables(input_vars); + for (const auto& vars : agent_vars_groups) { + var_mgr->create_named_variables(vars); + } + var_mgr->partition_variables(input_vars, agent_vars_groups); + + std::cout << "Input variables: " << std::endl; + for (const auto& var : input_vars) { + std::cout << var << std::endl; + } + for (std::size_t i = 0; i < agent_vars_groups.size(); ++i) { + std::cout << "Agent " << i << " variables: " << std::endl; + for (const auto& var : agent_vars_groups[i]) { + std::cout << var << std::endl; + } + } + + std::vector actors; + actors.push_back(Actor::Environment()); + for (size_t i = 0; i < num_agents; ++i) { + actors.push_back((i == 0) ? Actor::MainAgent() : Actor::PeerAgent(i)); + } + + //Formulas + std::vector formula_files = { root_dir / "env.ltlf" }; + for (size_t i = 0; i < num_agents; ++i) { + formula_files.push_back(root_dir / ("agent" + std::to_string(i) + ".ltlf")); + } + + // LTLf+ driver + std::shared_ptr driver = + std::make_shared(); + + + //Step 1) For each actor, construct the DWA of its safety/guarantuee specification + std::vector dwas; + std::size_t i = 0; + for (const auto& filename : formula_files) { + std::ifstream file(filename); + if (!file.is_open()) throw std::runtime_error("Impossible to open file " + filename.string()); + std::stringstream buffer; buffer << file.rdbuf(); + std::stringstream formula_stream(buffer.str()); + driver->parse(formula_stream); + auto result = driver->get_result(); + + // cast ast_ptr into ltlf_plus_ptr. Necessary since AbstractDriver is not template anymore + auto ptr_ltlf_plus_formula = + std::static_pointer_cast(result); + + // transform formula in PNF + auto pnf = whitemech::lydia::get_pnf_result(*ptr_ltlf_plus_formula); + Syft::LTLfPlus ltlf_plus_formula; + ltlf_plus_formula.color_formula_ = pnf.color_formula_; + ltlf_plus_formula.formula_to_color_= pnf.subformula_to_color_; + ltlf_plus_formula.formula_to_quantification_= pnf.subformula_to_quantifier_; + const auto& current_actor = actors[i]; + ObligationLTLfPlusSynthesizer synthesizer( + ltlf_plus_formula, + partition, + Actor::MainAgent(), // starting actor + current_actor, // protagonist actor + var_mgr + ); + + //we have to convert the ltlf plus formula into a symbolic dwa through function convert_to_symbolic + auto [final_dwa, color_to_final_states] = synthesizer.convert_to_symbolic_dfa(); + dwas.push_back(std::move(final_dwa)); + + ++i; + } + + //Step 2) From the environment's automata, construct the dwa that accepts CORE_env(phi_env) + + //SymbolicStateDfa R_env = SymbolicStateDfa::get_CORE(dwas[0], actors[0], Actor::MainAgent()); + SymbolicStateDfa env_ne = SymbolicStateDfa::get_NE(dwas[0], actors[0]); + SymbolicStateDfa R_env = SymbolicStateDfa::get_CORE(env_ne, actors[0], Actor::MainAgent()); + SymbolicStateDfa R_env_comp = SymbolicStateDfa::complement(R_env); + // std::cout << "Complement of restricted environment has initial state as accepting: "; + // std::cout << R_env_comp.final_states().Eval(R_env_comp.initial_state().data()).IsOne() << std::endl; + + + //Step 3) For each peer agent w + // step 3.1) costruct a new automaton obtained as follows: comp(R_env) U dwa_w + // step 3.2) construct a new automaton that accepts CORE_w( CORE_env(phi_env) -> phi_w) + + //std::vector R_peers; + std::vector R_peers_comp; + for (size_t i = 2; i < actors.size() ; ++i){ + std::vector vector; + vector.push_back(dwas[i]); + vector.push_back(R_env_comp); + SymbolicStateDfa dwa_prime = SymbolicStateDfa::product_OR(vector); + SymbolicStateDfa dwa_prime_ne = SymbolicStateDfa::get_NE(dwa_prime, actors[i]); + SymbolicStateDfa R_peer = SymbolicStateDfa::get_CORE(dwa_prime_ne, actors[i], Actor::MainAgent()); + //R_peers.push_back(R_peer); + + SymbolicStateDfa R_peer_comp = SymbolicStateDfa::complement(R_peer); + R_peers_comp.push_back(R_peer_comp); + // std::cout << "Complement of restricted peer has initial state as accepting: "; + // std::cout << R_peer_comp.final_states().Eval(R_peer_comp.initial_state().data()).IsOne() << std::endl; + } + + //Step 4) Costruct a new dwa for the main agent obtained as follows: R_env_comp U R_peers_comp U dwa_0 + std::vector vector; + vector.push_back(R_env_comp); + vector.insert( + vector.end(), + R_peers_comp.begin(), + R_peers_comp.end() + ); + vector.push_back(dwas[1]); + + SymbolicStateDfa arena = SymbolicStateDfa::product_OR(vector); + //std::cout << "arena has initial state accepting? " << arena.final_states().Eval(arena.initial_state().data()).IsOne() << std::endl; + + SymbolicStateDfa arena_ne = SymbolicStateDfa::get_NE(arena, Actor::MainAgent()); + //std::cout << "arena_ne has initial state accepting? " << arena_ne.final_states().Eval(arena_ne.initial_state().data()).IsOne() << std::endl; + + //var_mgr->print_mgr(); + + CUDD::BDD buchi_condition = arena_ne.final_states(); + //CUDD::BDD buchi_condition = arena.final_states(); + // std::cout << "Final states " << arena_ne.final_states() << std::endl; + // std::cout << "Final states " << arena.final_states() << std::endl; + CUDD::BDD space = var_mgr->cudd_mgr()->bddOne(); + + //protagonist actor is Main Agent + Actor protagonist_actor = Actor::MainAgent(); + //we can change the starting actor: now is the MainAgent(), but we can set as starting one also Enviroment() or PeerAgent(id) + Actor starting_actor = Actor::MainAgent(); + + + std::cout << "Protagonist actor: " << (protagonist_actor.is_environment() ? "ENV" : "Agent " + std::to_string(protagonist_actor.id())) << std::endl; + + //BuchiReachability_multiagent game(arena_ne, starting_actor, protagonist_actor, buchi_condition, space, num_agents); + BuchiReachability_multiagent game(arena_ne, starting_actor, protagonist_actor, buchi_condition, space, num_agents); + SynthesisResult result = game.run(); + + //std::cout << "[Main]: result.winning_states: " << result.winning_states << std::endl; + //std::cout << "[Main]: result.winning_moves: " << result.winning_moves << std::endl; + + + if (result.realizability) { + std::cout << "Result: TRUE " << std::endl; + if(result.transducer_multiagent != nullptr){ + std::string filename = "winning_strategy_" + (protagonist_actor.is_environment() ? "env" : "agent" + std::to_string(protagonist_actor.id())) + ".dot"; + result.transducer_multiagent->dump_dot(filename); + std::cout << "Winning strategy saved to " << filename << std::endl; + + std::cout << "\n------------------STARTING INTERACTIVE SIMULATION------------------\n"; + + + std::unordered_map id_to_var = var_mgr->get_index_to_name_map(); + + CUDD::BDD winning_region = result.winning_states; + std::unordered_map output_function; + + std::vector state = arena_ne.initial_state(); + //std::vector state = arena.initial_state(); + + size_t step_counter = 0; + std::size_t arena_id = arena_ne.automaton_id(); + //std::size_t arena_id = arena.automaton_id(); + + while (true) { + + std::cout << "\n\nSTEP " << step_counter << "\n\n"; + std::cout << "Current state bits: " << std::endl; + for(const auto& bit : state){ + std::cout << bit << " "; + } + std::cout << std::endl; + + output_function = result.transducer_multiagent.get()->get_output_function(); + + + // std::vector transition ( id_to_var.size(), 0); + std::vector transition = var_mgr->make_eval_vector(arena_id, state); + std::vector eval_state = var_mgr->make_eval_vector(arena_id, state); + + if (buchi_condition.Eval(eval_state.data()).IsOne()) { + std::cout << "\n[INFO] Main Agent is currently in an accepting state \n"; + } else { + std::cout << "\n[INFO] Main Agent is not in an accepting state \n"; + } + + std::cout << "Agent move: " << std::endl; + + for(int i = 0; i< id_to_var.size(); ++i){ + std::string var = id_to_var[i]; + if(var.empty() || !var_mgr->is_agent_variable(var, protagonist_actor.id())) continue; + + int agent_eval; + if(var_mgr->is_agent_variable(var, protagonist_actor.id())){ + std::cout << "Variable: " << var; + std::cout << ". Agent Output (0=false, 1=true): "; + agent_eval = output_function[i].Eval(eval_state.data()).IsOne() ? 1 : 0; + std::cout << agent_eval << std::endl; + transition[i] = agent_eval; + } + } + + //PEER MOVE + for (size_t peer_idx = 1; peer_idx < num_agents; ++peer_idx) { + std::cout << "\nPeer Agent " << peer_idx + << " move (type 1 if var is true, else 0): " << std::endl; + + for (int i = 0; i < id_to_var.size(); ++i) { + std::string var = id_to_var[i]; + if (var.empty()) continue; + + int peer_eval; + if (var_mgr->is_agent_variable(var, peer_idx)) { + + std::cout << "Variable: " << var; + std::cout << ". Peer Agent " << peer_idx << " Output (0=false, 1=true): "; + std::cin >> peer_eval; + transition[i] = peer_eval; + } + } + } + + //ENVIRONMENT MOVE + std::cout << "\nEnvironment move (type 1 if var is true, else 0): " << std::endl; + for (int i = 0; i < id_to_var.size(); ++i) { + std::string var = id_to_var[i]; + if (var.empty()) continue; + + int env_eval; + if (var_mgr->is_input_variable(var)) { + std::cout << "Variable: " << var; + std::cout << ". Environment Input (0=false, 1=true): "; + std::cin >> env_eval; + transition[i] = env_eval; + } + } + + std::cout << "Input to transitions: "; + for(const auto&b : transition) std::cout << b << " "; + std::cout << std::endl; + + for(int i = 0; iname_to_variable(id_to_var[i]).NodeReadIndex(); + if(bdd_idx < eval_state.size()){ + eval_state[bdd_idx] = transition[i]; + } + } + + bool is_valid_winning_moves = result.winning_moves.Eval(eval_state.data()).IsOne(); + //std::cout << "[Interactive debugger] The inserted transition comply with winning_moves? " << (is_valid_winning_moves? "YES": "NO") << std::endl; + + int curr_state_var = 0; + + std::vector new_state = state; + + for(int i = 0; i < arena_ne.transition_function().size(); ++i){ + new_state[curr_state_var] = arena_ne.transition_function()[i].Eval(eval_state.data()).IsOne() ? 1 : 0; + ++curr_state_var; + } + + std::cout << "Next state bits: " << std::endl; + for(const auto& bit : new_state){ + std::cout << bit << " "; + } + std::cout << std::endl; + + state = new_state; + step_counter++; + + std::vector eval_state_updated = var_mgr->make_eval_vector(arena_id,state); + + + // if (buchi_condition.Eval(eval_state_updated.data()).IsOne()) { + // std::cout << "\n[INFO] Main Agent is currently in an accepting state \n"; + // } else { + // std::cout << "\n[INFO] Main Agent is not in an accepting state \n"; + // } + + // char continue_choice; + // std::cout << "\nContinue to next step? (y/n): "; + // std::cin >> continue_choice; + // if (continue_choice == 'n' || continue_choice == 'N') { + // std::cout << "Simulation ended by user.\n"; + // break; + // } + } + + + + } + } else { + std::cout << "Result: FALSE " << std::endl; + } + + + + } catch (const std::exception& e) { + std::cerr << "[FATAL ERROR] " << e.what() << std::endl; + return 1; + } + return 0; +} \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7c919afc..f02a8d35 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -7,6 +7,11 @@ add_subdirectory(05_ltlf_synthesis_maximally_permissive) add_subdirectory(06_ltlf_synthesis_with_fairness_conditions) add_subdirectory(07_ltlf_synthesis_with_stability_conditions) add_subdirectory(08_ltlf_synthesis_with_gr1_env_spec) +add_subdirectory(09_reachability) +add_subdirectory(10_buchi_reachability) +add_subdirectory(11_obligation) +add_subdirectory(12_algorithm) +add_subdirectory(12_algorithm_interactive) add_custom_target(examples) @@ -19,4 +24,8 @@ add_dependencies(examples ltlf_synthesis_with_fairness_conditions_example ltlf_synthesis_with_stability_conditions_example ltlf_synthesis_with_gr1_env_spec_example + reachability_example + buchi_reachability_example + algorithm_example + algorithm_interactive_example ) diff --git a/examples/Wumpus.py b/examples/Wumpus.py new file mode 100644 index 00000000..9826ba48 --- /dev/null +++ b/examples/Wumpus.py @@ -0,0 +1,144 @@ +import os + +def generate_final_specs_and_partition(): + + root_path = "" + + rows, cols = 4, 4 + gold_positions = [(0, 1), (1, 2), (3, 2)] + wumpus_start = (2, 3) + agent_start = (3, 0) + destination = (0, 0) + + actions = ["nord", "sud", "est", "ovest", "wait"] + dest_str = f"at{destination[0]}{destination[1]}" + + + output_dir = os.path.join(root_path, "wumpus_game") + os.makedirs(output_dir, exist_ok=True) + + # # 1. FILE GENERATION: agent0.ltlf + + # # Sub-specification: Fcollect + fcollect_parts = [f"F(at{r}{c})" for r, c in gold_positions] + fcollect = " && ".join(fcollect_parts) + + # Sub-specification: Ftask + ftask_pure = f"E(F(({fcollect}) && X[!](F({dest_str} && X(G(wait))))))" + + # Sub-specification: Fmutex + fmutex_parts = [] + for i, act in enumerate(actions): + other_actions = [f"!({other})" for j, other in enumerate(actions) if i != j] + others_combined = " && ".join(other_actions) + fmutex_parts.append(f"({act} -> ({others_combined}))") + fmutex_pure = "A(G(" + " && ".join(fmutex_parts) + "))" + + # # Sub-specification: Favoid_wumpus + avoid_rows_list = [] + for r in range(rows): + row_cells = [] + for c in range(cols): + row_cells.append(f"(at{r}{c} && wat{r}{c})") + inner_row_or = " || ".join(row_cells) + avoid_rows_list.append(f"A(G(!({inner_row_or})))") + + favoid_pure = " && ".join(avoid_rows_list) + + + + # Sub-specification: Fpre + fpre_parts = [] + for r in range(rows): + for c in range(cols): + if r == 0: fpre_parts.append(f"(at{r}{c} -> !nord)") + if r == rows - 1: fpre_parts.append(f"(at{r}{c} -> !sud)") + if c == 0: fpre_parts.append(f"(at{r}{c} -> !ovest)") + if c == cols - 1: fpre_parts.append(f"(at{r}{c} -> !est)") + fpre_pure = "A(G(" + " && ".join(fpre_parts) + "))" + + # Final combined formula for Agent 0 + # {favoid_pure} && {ftask_pure} && {fmutex_pure} && {fpre_pure} + f0_pure = f"{favoid_pure} && {ftask_pure} && {fmutex_pure} && {fpre_pure}" + + with open(os.path.join(output_dir, "agent0.ltlf"), "w") as f: + f.write(f0_pure) + print("File 'agent0.ltlf' successfully saved.") + + # 2. FILE GENERATION: agent1.ltlf + + wumpus_cells = [f"wat{r}{3}" for r in range(rows)] + fwumpus_pure = "A(G(" + " || ".join(wumpus_cells) + "))" + + with open(os.path.join(output_dir, "agent1.ltlf"), "w") as f: + f.write(fwumpus_pure) + print("File 'agent1.ltlf' successfully saved.") + + # 3. FILE GENERATION: env.ltlf + + einit_parts = [] + for r in range(rows): + for c in range(cols): + if (r, c) == agent_start: einit_parts.append(f"at{r}{c}") + else: einit_parts.append(f"!(at{r}{c})") + + if (r, c) == wumpus_start: einit_parts.append(f"wat{r}{c}") + else: einit_parts.append(f"!(wat{r}{c})") + einit_pure = " && ".join(einit_parts) + + etrans_list = [] + + for r in range(rows): + for c in range(cols): + incoming = [] + if r + 1 < rows: incoming.append(f"(at{r+1}{c} && nord)") + if r - 1 >= 0: incoming.append(f"(at{r-1}{c} && sud)") + if c - 1 >= 0: incoming.append(f"(at{r}{c-1} && est)") + if c + 1 < cols: incoming.append(f"(at{r}{c+1} && ovest)") + + incoming.append(f"(at{r}{c} && wait)") + + incoming = list(set(incoming)) + condition = " || ".join(incoming) + + etrans_list.append(f"X(G(X(at{r}{c} <-> ({condition}))))") + + + for r in range(rows): + wumpus_incoming = [] + if r + 1 < rows: wumpus_incoming.append(f"wat{r+1}3") + if r - 1 >= 0: wumpus_incoming.append(f"wat{r-1}3") + + wumpus_condition = " || ".join(wumpus_incoming) + + etrans_list.append(f"X(G(X(wat{r}3 <-> ({wumpus_condition}))))") + + + etrans_pure = " && ".join(etrans_list) + # fenv_pure = f"({einit_pure}) && A({etrans_pure})" + fenv_pure = f"A({etrans_pure})" + + with open(os.path.join(output_dir, "env.ltlf"), "w") as f: + f.write(fenv_pure) + print("File 'env.ltlf' successfully saved.") + + # # 4. FILE GENERATION: var.part + + part_lines = [] + part_lines.append(".inputs:") + + agent0_vars = [f"at{r}{c}" for r in range(rows) for c in range(cols)] + agent0_vars.extend(actions) + part_lines.append(".agent0:" + " ".join(agent0_vars)) + + agent1_vars = [f"wat{r}{c}" for r in range(rows) for c in range(cols)] + part_lines.append(".agent1:" + " ".join(agent1_vars)) + + part_path = os.path.join(output_dir, "var.part") + with open(part_path, "w") as f: + f.write("\n".join(part_lines)) + print("File 'var.part' successfully saved.") + + +if __name__ == "__main__": + generate_final_specs_and_partition() diff --git a/examples/WumpusGame.py b/examples/WumpusGame.py new file mode 100644 index 00000000..b871ae29 --- /dev/null +++ b/examples/WumpusGame.py @@ -0,0 +1,293 @@ +import os + +def generate_final_specs_and_partition(): + root_path = "" + + + # # Gioco 2 x 2 + # rows, cols = 2, 2 + # gold_positions = [(0, 0)] + # wumpus_start = (1, 1) + # agent_start = (1, 0) + # destination = (1, 0) + + # Gioco 3 x 3 + rows, cols = 3, 3 + gold_positions = [(2, 0),(1, 1), (0, 2),(0, 0)] + wumpus1_start = (0, 2) + wumpus2_start = (2, 2) + agent_start = (1, 0) + + + #4 x 4 game + # rows, cols = 4, 4 + # gold_positions = [(0, 1), (1, 2), (3, 2)] + # wumpus_start = (2, 3) + # agent_start = (3, 0) + # destination = (0, 0) + + agent_actions = ["anorth", "asouth", "aeast", "awest"] + wumpus1_actions = ["w1north", "w1south", "w1east", "w1west"] + wumpus2_actions = ["w2north", "w2south", "w2east", "w2west"] + + + output_dir = os.path.join(root_path, "wumpus_game") + os.makedirs(output_dir, exist_ok=True) + + # ---------------------------------------------------- + # 1. GENERAZIONE FILE: agent0.ltlf + # ---------------------------------------------------- + # Sub-specification: Fcollect + fcollect_parts = [f"F(at{r}{c})" for r, c in gold_positions] + fcollect = " && ".join(fcollect_parts) + + # Sub-specification: Ftask + ftask_pure = f"E(F(({fcollect})))" + + # Sub-specification: Fmutex + fmutex_or = " || ".join(agent_actions) + fmutex_imp_parts = [] + for i, act in enumerate(agent_actions): + other_actions = [f"!{other}" for j, other in enumerate(agent_actions) if i != j] + others_combined = " && ".join(other_actions) + fmutex_imp_parts.append(f"({act} -> ({others_combined}))") + fmutex_imp = " && ".join(fmutex_imp_parts) + fmutex_pure = f"A(G({fmutex_or} && {fmutex_imp}))" + + + # Sub-specification: Favoid_wumpus + w1_collision_cells = [f"(at{r}{c} && w1at{r}{c})" for r in range(rows) for c in range(cols)] + w1_all_collisions_or = " || ".join(w1_collision_cells) + favoid1_pure = f"A(G(!({w1_all_collisions_or})))" + # Sub-specification: Favoid_wumpus + w2_collision_cells = [f"(at{r}{c} && w2at{r}{c})" for r in range(rows) for c in range(cols)] + w2_all_collisions_or = " || ".join(w2_collision_cells) + favoid2_pure = f"A(G(!({w2_all_collisions_or})))" + + f0_pure = f"{ftask_pure} && {fmutex_pure} && {favoid1_pure} && {favoid2_pure}" + + + with open(os.path.join(output_dir, "agent0.ltlf"), "w") as f: + f.write(f0_pure) + print("File 'agent0.ltlf' salvato con successo.") + + + # ---------------------------------------------------- + # 2. GENERAZIONE FILE: agent1.ltlf (Wumpus) + # ---------------------------------------------------- + wumpus1_cells = [f"w1at{r}{cols-1}" for r in range(rows)] + fwumpus1_pure = "A(G(" + " || ".join(wumpus1_cells) + "))" + + + fmutex_w1_or = " || ".join(wumpus1_actions) + fmutex_w1_imp_parts = [] + for i, act in enumerate(wumpus1_actions): + other_wactions = [f"!{other}" for j, other in enumerate(wumpus1_actions) if i != j] + others_wcombined = " && ".join(other_wactions) + fmutex_w1_imp_parts.append(f"({act} -> ({others_wcombined}))") + fmutex_w1_imp = " && ".join(fmutex_w1_imp_parts) + + c_idx = cols - 1 + patrol_clauses = [] + for r in range(rows): + current_var = f"w1at{r}{c_idx}" + if r == 0: + next_vars = f"w1at{r+1}{c_idx}" + elif r == rows - 1: + next_vars = f"w1at{r-1}{c_idx}" + else: + next_vars = f"w1at{r-1}{c_idx} || w1at{r+1}{c_idx}" + patrol_clauses.append(f"({current_var} -> X({next_vars}))") + w_patrol_spec = "A(G(" + " && ".join(patrol_clauses) + "))" + + + fmutex_w1 = f"A(G({fmutex_w1_or} && {fmutex_w1_imp})) && {w_patrol_spec}" + + f1_pure = f"{fwumpus1_pure} && {fmutex_w1}" + with open(os.path.join(output_dir, "agent1.ltlf"), "w") as f: + f.write(f1_pure) + print("File 'agent1.ltlf' saved.") + + # ---------------------------------------------------- + # 2. GENERAZIONE FILE: agent2.ltlf (Wumpus) + # ---------------------------------------------------- + #it can be only in the last row + wumpus2_cells = [f"w2at{rows-1}{c}" for c in range(cols)] + fwumpus2_pure = "A(G(" + " || ".join(wumpus2_cells) + "))" + + + fmutex_w2_or = " || ".join(wumpus2_actions) + fmutex_w2_imp_parts = [] + for i, act in enumerate(wumpus2_actions): + other_wactions = [f"!{other}" for j, other in enumerate(wumpus2_actions) if i != j] + others_wcombined = " && ".join(other_wactions) + fmutex_w2_imp_parts.append(f"({act} -> ({others_wcombined}))") + fmutex_w2_imp = " && ".join(fmutex_w2_imp_parts) + + r_idx = rows - 1 + patrol_clauses = [] + for c in range(cols): + current_var = f"w2at{r_idx}{c}" + if c == 0: + next_vars = f"w2at{r_idx}{c+1}" + elif c == cols - 1: + next_vars = f"w2at{r_idx}{c-1}" + else: + next_vars = f"w2at{r_idx}{c-1} || w2at{r_idx}{c+1}" + patrol_clauses.append(f"({current_var} -> X({next_vars}))") + w_patrol_spec = "A(G(" + " && ".join(patrol_clauses) + "))" + + + fmutex_w2 = f"A(G({fmutex_w2_or} && {fmutex_w2_imp})) && {w_patrol_spec}" + + f2_pure = f"{fwumpus2_pure} && {fmutex_w2}" + with open(os.path.join(output_dir, "agent2.ltlf"), "w") as f: + f.write(f2_pure) + print("File 'agent2.ltlf' saved.") + + + # ---------------------------------------------------- + # 3. GENERAZIONE FILE: env.ltlf + # ---------------------------------------------------- + def get_grid_state_str(target_r, target_c, max_rows, max_cols, prefix): + state_parts = [] + for r in range(max_rows): + for c in range(max_cols): + if r == target_r and c == target_c: + state_parts.append(f"{prefix}{r}{c}") + else: + state_parts.append(f"!{prefix}{r}{c}") + return " && ".join(state_parts) + + #Initial states for agent and wumpus + init_a_parts = [] + init_w1_parts = [] + init_w2_parts = [] + for r in range(rows): + for c in range(cols): + if (r, c) == agent_start: init_a_parts.append(f"at{r}{c}") + else: init_a_parts.append(f"!at{r}{c}") + + if (r, c) == wumpus1_start: init_w1_parts.append(f"w1at{r}{c}") + else: init_w1_parts.append(f"!w1at{r}{c}") + + if (r, c) == wumpus2_start: init_w2_parts.append(f"w2at{r}{c}") + else: init_w2_parts.append(f"!w2at{r}{c}") + init_agent_str = " && ".join(init_a_parts) + init_wumpus1_str = " && ".join(init_w1_parts) + init_wumpus2_str = " && ".join(init_w2_parts) + + # mutex body for agent and wumpus + a_or = "(" + " || ".join(agent_actions) + ")" + a_imp_list = [] + for i, act in enumerate(agent_actions): + others = [f"!{o}" for j, o in enumerate(agent_actions) if i != j] + a_imp_list.append(f"({act} -> ({' && '.join(others)}))") + + mutex_body_agent = f"{a_or} && " + " && ".join(a_imp_list) + + w1_or = "(" + " || ".join(wumpus1_actions) + ")" + w1_imp_list = [] + for i, act in enumerate(wumpus1_actions): + others = [f"!{o}" for j, o in enumerate(wumpus1_actions) if i != j] + w1_imp_list.append(f"({act} -> ({' && '.join(others)}))") + + mutex_body_wumpus1 = f"{w1_or} && " + " && ".join(w1_imp_list) + + w2_or = "(" + " || ".join(wumpus2_actions) + ")" + w2_imp_list = [] + for i, act in enumerate(wumpus2_actions): + others = [f"!{o}" for j, o in enumerate(wumpus2_actions) if i != j] + w2_imp_list.append(f"({act} -> ({' && '.join(others)}))") + + mutex_body_wumpus2 = f"{w2_or} && " + " && ".join(w2_imp_list) + + #agent transitions + etrans_list_agent = [] + for r in range(rows): + for c in range(cols): + for act in agent_actions: + next_r, next_c = r, c + if "north" in act or "nord" in act: + if r > 0: next_r = r - 1 + elif "south" in act or "sud" in act: + if r < rows - 1: next_r = r + 1 + elif "west" in act or "ovest" in act: + if c > 0: next_c = c - 1 + elif "east" in act or "est" in act: + if c < cols - 1: next_c = c + 1 + + next_state = get_grid_state_str(next_r, next_c, rows, cols, "at") + etrans_list_agent.append(f"((at{r}{c} && {act}) -> X({next_state}))") + a_etrans_pure = " && ".join(etrans_list_agent) + + # wumpus transitions + etrans_list_wumpus1 = [] + for r in range(rows): + for c in range(cols): + for act in wumpus1_actions: + next_r, next_c = r, c + if "north" in act or "nord" in act: + if r > 0: next_r = r - 1 + elif "south" in act or "sud" in act: + if r < rows - 1: next_r = r + 1 + elif "west" in act or "ovest" in act: + if c > 0: next_c = c - 1 + elif "east" in act or "est" in act: + if c < cols - 1: next_c = c + 1 + + next_state = get_grid_state_str(next_r, next_c, rows, cols, "w1at") + etrans_list_wumpus1.append(f"((w1at{r}{c} && {act}) -> X({next_state}))") + w1_etrans_pure = " && ".join(etrans_list_wumpus1) + + # wumpus transitions + etrans_list_wumpus2 = [] + for r in range(rows): + for c in range(cols): + for act in wumpus2_actions: + next_r, next_c = r, c + if "north" in act or "nord" in act: + if r > 0: next_r = r - 1 + elif "south" in act or "sud" in act: + if r < rows - 1: next_r = r + 1 + elif "west" in act or "ovest" in act: + if c > 0: next_c = c - 1 + elif "east" in act or "est" in act: + if c < cols - 1: next_c = c + 1 + + next_state = get_grid_state_str(next_r, next_c, rows, cols, "w2at") + etrans_list_wumpus2.append(f"((w2at{r}{c} && {act}) -> X({next_state}))") + w2_etrans_pure = " && ".join(etrans_list_wumpus2) + + agent_block = f"A(G({mutex_body_agent}) -> (({init_agent_str}) && G({a_etrans_pure})))" + wumpus1_block = f"A(G({mutex_body_wumpus1}) -> (({init_wumpus1_str}) && G({w1_etrans_pure})))" + wumpus2_block = f"A(G({mutex_body_wumpus2}) -> (({init_wumpus2_str}) && G({w2_etrans_pure})))" + fenv_pure = f"({agent_block}) && ({wumpus1_block}) && ({wumpus2_block})" + + with open(os.path.join(output_dir, "env.ltlf"), "w") as f: + f.write(fenv_pure) + print("File 'env.ltlf' saved.") + + # ---------------------------------------------------- + # 4. GENERAZIONE FILE: var.part + # ---------------------------------------------------- + part_lines = [] + agent0_vars = [f"at{r}{c}" for r in range(rows) for c in range(cols)] + agent1_vars = [f"w1at{r}{c}" for r in range(rows) for c in range(cols)] + agent2_vars = [f"w2at{r}{c}" for r in range(rows) for c in range(cols)] + + #part_lines.append(".inputs: " + " ".join(agent0_vars)) + part_lines.append(".inputs: " + " ".join(agent0_vars) + " " + " ".join(agent1_vars)+ " " + " ".join(agent2_vars)) + part_lines.append(".agent0: " + " ".join(agent_actions)) + part_lines.append(".agent1: " + " ".join(wumpus1_actions)) + part_lines.append(".agent2: " + " ".join(wumpus2_actions)) + + part_path = os.path.join(output_dir, "var.part") + with open(part_path, "w") as f: + f.write("\n".join(part_lines)) + print("File 'var.part' saved.") + + +if __name__ == "__main__": + generate_final_specs_and_partition() + diff --git a/examples/test.tlsf b/examples/test.tlsf index 4504d2d7..cf3fbd6e 100644 --- a/examples/test.tlsf +++ b/examples/test.tlsf @@ -8,8 +8,7 @@ INFO { MAIN { INPUTS { - a1; - a2; + a1; a2; } OUTPUTS { diff --git a/examples/test/agent0.ltlf b/examples/test/agent0.ltlf new file mode 100644 index 00000000..9d08f803 --- /dev/null +++ b/examples/test/agent0.ltlf @@ -0,0 +1 @@ +E(F(a && b && !(c))) \ No newline at end of file diff --git a/examples/test/agent1.ltlf b/examples/test/agent1.ltlf new file mode 100644 index 00000000..ade50641 --- /dev/null +++ b/examples/test/agent1.ltlf @@ -0,0 +1 @@ +E(F(b)) \ No newline at end of file diff --git a/examples/test/env.ltlf b/examples/test/env.ltlf new file mode 100644 index 00000000..ff69bddc --- /dev/null +++ b/examples/test/env.ltlf @@ -0,0 +1 @@ +A(G(a)) \ No newline at end of file diff --git a/examples/test/var.part b/examples/test/var.part new file mode 100644 index 00000000..8031d75a --- /dev/null +++ b/examples/test/var.part @@ -0,0 +1,3 @@ +.inputs:a +.agent0:c +.agent1:b \ No newline at end of file diff --git a/examples/test_algo_real/agent0.ltlf b/examples/test_algo_real/agent0.ltlf new file mode 100644 index 00000000..cf1c6428 --- /dev/null +++ b/examples/test_algo_real/agent0.ltlf @@ -0,0 +1 @@ +E(stable && X(complete)) \ No newline at end of file diff --git a/examples/test_algo_real/agent1.ltlf b/examples/test_algo_real/agent1.ltlf new file mode 100644 index 00000000..1a0a70c0 --- /dev/null +++ b/examples/test_algo_real/agent1.ltlf @@ -0,0 +1 @@ +A(!(sensor && start)) || E(power) \ No newline at end of file diff --git a/examples/test_algo_real/agent2.ltlf b/examples/test_algo_real/agent2.ltlf new file mode 100644 index 00000000..2d794c8e --- /dev/null +++ b/examples/test_algo_real/agent2.ltlf @@ -0,0 +1 @@ +A(power -> stable) \ No newline at end of file diff --git a/examples/test_algo_real/env.ltlf b/examples/test_algo_real/env.ltlf new file mode 100644 index 00000000..900fe736 --- /dev/null +++ b/examples/test_algo_real/env.ltlf @@ -0,0 +1 @@ +A(!(start) -> X(!(sensor))) \ No newline at end of file diff --git a/examples/test_algo_real/var.part b/examples/test_algo_real/var.part new file mode 100644 index 00000000..ebdf2417 --- /dev/null +++ b/examples/test_algo_real/var.part @@ -0,0 +1,4 @@ +.inputs:sensor +.agent0:start complete +.agent1:power +.agent2:stable \ No newline at end of file diff --git a/examples/test_algo_unreal/agent0.ltlf b/examples/test_algo_unreal/agent0.ltlf new file mode 100644 index 00000000..0f03bec6 --- /dev/null +++ b/examples/test_algo_unreal/agent0.ltlf @@ -0,0 +1 @@ +E(stable && complete && power) \ No newline at end of file diff --git a/examples/test_algo_unreal/agent1.ltlf b/examples/test_algo_unreal/agent1.ltlf new file mode 100644 index 00000000..1a0a70c0 --- /dev/null +++ b/examples/test_algo_unreal/agent1.ltlf @@ -0,0 +1 @@ +A(!(sensor && start)) || E(power) \ No newline at end of file diff --git a/examples/test_algo_unreal/agent2.ltlf b/examples/test_algo_unreal/agent2.ltlf new file mode 100644 index 00000000..2e039f3f --- /dev/null +++ b/examples/test_algo_unreal/agent2.ltlf @@ -0,0 +1 @@ +A(power -> X(stable)) \ No newline at end of file diff --git a/examples/test_algo_unreal/env.ltlf b/examples/test_algo_unreal/env.ltlf new file mode 100644 index 00000000..900fe736 --- /dev/null +++ b/examples/test_algo_unreal/env.ltlf @@ -0,0 +1 @@ +A(!(start) -> X(!(sensor))) \ No newline at end of file diff --git a/examples/test_algo_unreal/var.part b/examples/test_algo_unreal/var.part new file mode 100644 index 00000000..ebdf2417 --- /dev/null +++ b/examples/test_algo_unreal/var.part @@ -0,0 +1,4 @@ +.inputs:sensor +.agent0:start complete +.agent1:power +.agent2:stable \ No newline at end of file diff --git a/examples/test_buchi/agent0.ltlf b/examples/test_buchi/agent0.ltlf new file mode 100644 index 00000000..ff5d24d3 --- /dev/null +++ b/examples/test_buchi/agent0.ltlf @@ -0,0 +1 @@ +G(req0 && (!req0)) \ No newline at end of file diff --git a/examples/test_buchi/agent1.ltlf b/examples/test_buchi/agent1.ltlf new file mode 100644 index 00000000..0bdadc73 --- /dev/null +++ b/examples/test_buchi/agent1.ltlf @@ -0,0 +1 @@ +G(!(req0)) \ No newline at end of file diff --git a/examples/test_buchi/agent2.ltlf b/examples/test_buchi/agent2.ltlf new file mode 100644 index 00000000..a9386658 --- /dev/null +++ b/examples/test_buchi/agent2.ltlf @@ -0,0 +1 @@ +G(p2 || !(p2)) \ No newline at end of file diff --git a/examples/test_buchi/env.ltlf b/examples/test_buchi/env.ltlf new file mode 100644 index 00000000..a319888f --- /dev/null +++ b/examples/test_buchi/env.ltlf @@ -0,0 +1 @@ +G(grant -> ready) \ No newline at end of file diff --git a/examples/test_buchi/var.part b/examples/test_buchi/var.part new file mode 100644 index 00000000..389643a2 --- /dev/null +++ b/examples/test_buchi/var.part @@ -0,0 +1,4 @@ +.inputs:ready grant +.agent0:req0 +.agent1:req1 +.agent2:p2 \ No newline at end of file diff --git a/examples/test_obligation/agent0.ltlf b/examples/test_obligation/agent0.ltlf new file mode 100644 index 00000000..47ab4c18 --- /dev/null +++ b/examples/test_obligation/agent0.ltlf @@ -0,0 +1 @@ +(A(!(b && c))) && (A(a & X(a) -> X(b || c))) && (A(b -> X(!b))) \ No newline at end of file diff --git a/examples/test_obligation/agent1.ltlf b/examples/test_obligation/agent1.ltlf new file mode 100644 index 00000000..e09f8059 --- /dev/null +++ b/examples/test_obligation/agent1.ltlf @@ -0,0 +1 @@ +(E(d && X(e))) && (A(e -> !d)) \ No newline at end of file diff --git a/examples/test_obligation/agent2.ltlf b/examples/test_obligation/agent2.ltlf new file mode 100644 index 00000000..ffe0c6f7 --- /dev/null +++ b/examples/test_obligation/agent2.ltlf @@ -0,0 +1 @@ +A(c -> (!g U f)) \ No newline at end of file diff --git a/examples/test_obligation/env.ltlf b/examples/test_obligation/env.ltlf new file mode 100644 index 00000000..4d08cfc9 --- /dev/null +++ b/examples/test_obligation/env.ltlf @@ -0,0 +1 @@ +A(a -> X(!a | X(!a))) \ No newline at end of file diff --git a/examples/test_obligation/var.part b/examples/test_obligation/var.part new file mode 100644 index 00000000..4757818f --- /dev/null +++ b/examples/test_obligation/var.part @@ -0,0 +1,4 @@ +.inputs:a +.agent0:b c +.agent1:d e +.agent2:f g \ No newline at end of file diff --git a/examples/test_reachability/agent0.ltlf b/examples/test_reachability/agent0.ltlf new file mode 100644 index 00000000..9ed377cd --- /dev/null +++ b/examples/test_reachability/agent0.ltlf @@ -0,0 +1 @@ +F(grant1) \ No newline at end of file diff --git a/examples/test_reachability/agent1.ltlf b/examples/test_reachability/agent1.ltlf new file mode 100644 index 00000000..523aa9af --- /dev/null +++ b/examples/test_reachability/agent1.ltlf @@ -0,0 +1 @@ +F(grant0 | grant1) \ No newline at end of file diff --git a/examples/test_reachability/agent2.ltlf b/examples/test_reachability/agent2.ltlf new file mode 100644 index 00000000..054d2fc3 --- /dev/null +++ b/examples/test_reachability/agent2.ltlf @@ -0,0 +1 @@ +F(busy) \ No newline at end of file diff --git a/examples/test_reachability/env.ltlf b/examples/test_reachability/env.ltlf new file mode 100644 index 00000000..417962dc --- /dev/null +++ b/examples/test_reachability/env.ltlf @@ -0,0 +1 @@ +G(ready) \ No newline at end of file diff --git a/examples/test_reachability/var.part b/examples/test_reachability/var.part new file mode 100644 index 00000000..3b513a91 --- /dev/null +++ b/examples/test_reachability/var.part @@ -0,0 +1,4 @@ +.inputs:ready +.agent0:req0 +.agent1:grant0 grant1 +.agent2:busy \ No newline at end of file diff --git a/examples/wumpus_game/agent0.ltlf b/examples/wumpus_game/agent0.ltlf new file mode 100644 index 00000000..33a21837 --- /dev/null +++ b/examples/wumpus_game/agent0.ltlf @@ -0,0 +1 @@ +E(F((F(at20) && F(at11) && F(at02) && F(at00)))) && A(G(anorth || asouth || aeast || awest && (anorth -> (!asouth && !aeast && !awest)) && (asouth -> (!anorth && !aeast && !awest)) && (aeast -> (!anorth && !asouth && !awest)) && (awest -> (!anorth && !asouth && !aeast)))) && A(G(!((at00 && w1at00) || (at01 && w1at01) || (at02 && w1at02) || (at10 && w1at10) || (at11 && w1at11) || (at12 && w1at12) || (at20 && w1at20) || (at21 && w1at21) || (at22 && w1at22)))) && A(G(!((at00 && w2at00) || (at01 && w2at01) || (at02 && w2at02) || (at10 && w2at10) || (at11 && w2at11) || (at12 && w2at12) || (at20 && w2at20) || (at21 && w2at21) || (at22 && w2at22)))) \ No newline at end of file diff --git a/examples/wumpus_game/agent1.ltlf b/examples/wumpus_game/agent1.ltlf new file mode 100644 index 00000000..004094b9 --- /dev/null +++ b/examples/wumpus_game/agent1.ltlf @@ -0,0 +1 @@ +A(G(w1at02 || w1at12 || w1at22)) && A(G(w1north || w1south || w1east || w1west && (w1north -> (!w1south && !w1east && !w1west)) && (w1south -> (!w1north && !w1east && !w1west)) && (w1east -> (!w1north && !w1south && !w1west)) && (w1west -> (!w1north && !w1south && !w1east)))) && A(G((w1at02 -> X(w1at12)) && (w1at12 -> X(w1at02 || w1at22)) && (w1at22 -> X(w1at12)))) \ No newline at end of file diff --git a/examples/wumpus_game/agent2.ltlf b/examples/wumpus_game/agent2.ltlf new file mode 100644 index 00000000..35100ae1 --- /dev/null +++ b/examples/wumpus_game/agent2.ltlf @@ -0,0 +1 @@ +A(G(w2at20 || w2at21 || w2at22)) && A(G(w2north || w2south || w2east || w2west && (w2north -> (!w2south && !w2east && !w2west)) && (w2south -> (!w2north && !w2east && !w2west)) && (w2east -> (!w2north && !w2south && !w2west)) && (w2west -> (!w2north && !w2south && !w2east)))) && A(G((w2at20 -> X(w2at21)) && (w2at21 -> X(w2at20 || w2at22)) && (w2at22 -> X(w2at21)))) \ No newline at end of file diff --git a/examples/wumpus_game/env.ltlf b/examples/wumpus_game/env.ltlf new file mode 100644 index 00000000..178d05aa --- /dev/null +++ b/examples/wumpus_game/env.ltlf @@ -0,0 +1 @@ +(A(G((anorth || asouth || aeast || awest) && (anorth -> (!asouth && !aeast && !awest)) && (asouth -> (!anorth && !aeast && !awest)) && (aeast -> (!anorth && !asouth && !awest)) && (awest -> (!anorth && !asouth && !aeast))) -> ((!at00 && !at01 && !at02 && at10 && !at11 && !at12 && !at20 && !at21 && !at22) && G(((at00 && anorth) -> X(at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at00 && asouth) -> X(!at00 && !at01 && !at02 && at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at00 && aeast) -> X(!at00 && at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at00 && awest) -> X(at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at01 && anorth) -> X(!at00 && at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at01 && asouth) -> X(!at00 && !at01 && !at02 && !at10 && at11 && !at12 && !at20 && !at21 && !at22)) && ((at01 && aeast) -> X(!at00 && !at01 && at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at01 && awest) -> X(at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at02 && anorth) -> X(!at00 && !at01 && at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at02 && asouth) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && at12 && !at20 && !at21 && !at22)) && ((at02 && aeast) -> X(!at00 && !at01 && at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at02 && awest) -> X(!at00 && at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at10 && anorth) -> X(at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at10 && asouth) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && at20 && !at21 && !at22)) && ((at10 && aeast) -> X(!at00 && !at01 && !at02 && !at10 && at11 && !at12 && !at20 && !at21 && !at22)) && ((at10 && awest) -> X(!at00 && !at01 && !at02 && at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at11 && anorth) -> X(!at00 && at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at11 && asouth) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && at21 && !at22)) && ((at11 && aeast) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && at12 && !at20 && !at21 && !at22)) && ((at11 && awest) -> X(!at00 && !at01 && !at02 && at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at12 && anorth) -> X(!at00 && !at01 && at02 && !at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at12 && asouth) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && at22)) && ((at12 && aeast) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && at12 && !at20 && !at21 && !at22)) && ((at12 && awest) -> X(!at00 && !at01 && !at02 && !at10 && at11 && !at12 && !at20 && !at21 && !at22)) && ((at20 && anorth) -> X(!at00 && !at01 && !at02 && at10 && !at11 && !at12 && !at20 && !at21 && !at22)) && ((at20 && asouth) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && at20 && !at21 && !at22)) && ((at20 && aeast) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && at21 && !at22)) && ((at20 && awest) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && at20 && !at21 && !at22)) && ((at21 && anorth) -> X(!at00 && !at01 && !at02 && !at10 && at11 && !at12 && !at20 && !at21 && !at22)) && ((at21 && asouth) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && at21 && !at22)) && ((at21 && aeast) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && at22)) && ((at21 && awest) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && at20 && !at21 && !at22)) && ((at22 && anorth) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && at12 && !at20 && !at21 && !at22)) && ((at22 && asouth) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && at22)) && ((at22 && aeast) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && !at21 && at22)) && ((at22 && awest) -> X(!at00 && !at01 && !at02 && !at10 && !at11 && !at12 && !at20 && at21 && !at22)))))) && (A(G((w1north || w1south || w1east || w1west) && (w1north -> (!w1south && !w1east && !w1west)) && (w1south -> (!w1north && !w1east && !w1west)) && (w1east -> (!w1north && !w1south && !w1west)) && (w1west -> (!w1north && !w1south && !w1east))) -> ((!w1at00 && !w1at01 && w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22) && G(((w1at00 && w1north) -> X(w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at00 && w1south) -> X(!w1at00 && !w1at01 && !w1at02 && w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at00 && w1east) -> X(!w1at00 && w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at00 && w1west) -> X(w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at01 && w1north) -> X(!w1at00 && w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at01 && w1south) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at01 && w1east) -> X(!w1at00 && !w1at01 && w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at01 && w1west) -> X(w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at02 && w1north) -> X(!w1at00 && !w1at01 && w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at02 && w1south) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at02 && w1east) -> X(!w1at00 && !w1at01 && w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at02 && w1west) -> X(!w1at00 && w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at10 && w1north) -> X(w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at10 && w1south) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && w1at20 && !w1at21 && !w1at22)) && ((w1at10 && w1east) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at10 && w1west) -> X(!w1at00 && !w1at01 && !w1at02 && w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at11 && w1north) -> X(!w1at00 && w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at11 && w1south) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && w1at21 && !w1at22)) && ((w1at11 && w1east) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at11 && w1west) -> X(!w1at00 && !w1at01 && !w1at02 && w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at12 && w1north) -> X(!w1at00 && !w1at01 && w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at12 && w1south) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && w1at22)) && ((w1at12 && w1east) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at12 && w1west) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at20 && w1north) -> X(!w1at00 && !w1at01 && !w1at02 && w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at20 && w1south) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && w1at20 && !w1at21 && !w1at22)) && ((w1at20 && w1east) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && w1at21 && !w1at22)) && ((w1at20 && w1west) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && w1at20 && !w1at21 && !w1at22)) && ((w1at21 && w1north) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && w1at11 && !w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at21 && w1south) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && w1at21 && !w1at22)) && ((w1at21 && w1east) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && w1at22)) && ((w1at21 && w1west) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && w1at20 && !w1at21 && !w1at22)) && ((w1at22 && w1north) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && w1at12 && !w1at20 && !w1at21 && !w1at22)) && ((w1at22 && w1south) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && w1at22)) && ((w1at22 && w1east) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && !w1at21 && w1at22)) && ((w1at22 && w1west) -> X(!w1at00 && !w1at01 && !w1at02 && !w1at10 && !w1at11 && !w1at12 && !w1at20 && w1at21 && !w1at22)))))) && (A(G((w2north || w2south || w2east || w2west) && (w2north -> (!w2south && !w2east && !w2west)) && (w2south -> (!w2north && !w2east && !w2west)) && (w2east -> (!w2north && !w2south && !w2west)) && (w2west -> (!w2north && !w2south && !w2east))) -> ((!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && w2at22) && G(((w2at00 && w2north) -> X(w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at00 && w2south) -> X(!w2at00 && !w2at01 && !w2at02 && w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at00 && w2east) -> X(!w2at00 && w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at00 && w2west) -> X(w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at01 && w2north) -> X(!w2at00 && w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at01 && w2south) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at01 && w2east) -> X(!w2at00 && !w2at01 && w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at01 && w2west) -> X(w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at02 && w2north) -> X(!w2at00 && !w2at01 && w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at02 && w2south) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at02 && w2east) -> X(!w2at00 && !w2at01 && w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at02 && w2west) -> X(!w2at00 && w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at10 && w2north) -> X(w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at10 && w2south) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && w2at20 && !w2at21 && !w2at22)) && ((w2at10 && w2east) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at10 && w2west) -> X(!w2at00 && !w2at01 && !w2at02 && w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at11 && w2north) -> X(!w2at00 && w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at11 && w2south) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && w2at21 && !w2at22)) && ((w2at11 && w2east) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at11 && w2west) -> X(!w2at00 && !w2at01 && !w2at02 && w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at12 && w2north) -> X(!w2at00 && !w2at01 && w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at12 && w2south) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && w2at22)) && ((w2at12 && w2east) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at12 && w2west) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at20 && w2north) -> X(!w2at00 && !w2at01 && !w2at02 && w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at20 && w2south) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && w2at20 && !w2at21 && !w2at22)) && ((w2at20 && w2east) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && w2at21 && !w2at22)) && ((w2at20 && w2west) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && w2at20 && !w2at21 && !w2at22)) && ((w2at21 && w2north) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && w2at11 && !w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at21 && w2south) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && w2at21 && !w2at22)) && ((w2at21 && w2east) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && w2at22)) && ((w2at21 && w2west) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && w2at20 && !w2at21 && !w2at22)) && ((w2at22 && w2north) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && w2at12 && !w2at20 && !w2at21 && !w2at22)) && ((w2at22 && w2south) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && w2at22)) && ((w2at22 && w2east) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && !w2at21 && w2at22)) && ((w2at22 && w2west) -> X(!w2at00 && !w2at01 && !w2at02 && !w2at10 && !w2at11 && !w2at12 && !w2at20 && w2at21 && !w2at22)))))) \ No newline at end of file diff --git a/examples/wumpus_game/var.part b/examples/wumpus_game/var.part new file mode 100644 index 00000000..eb53078f --- /dev/null +++ b/examples/wumpus_game/var.part @@ -0,0 +1,4 @@ +.inputs: at00 at01 at02 at10 at11 at12 at20 at21 at22 w1at00 w1at01 w1at02 w1at10 w1at11 w1at12 w1at20 w1at21 w1at22 w2at00 w2at01 w2at02 w2at10 w2at11 w2at12 w2at20 w2at21 w2at22 +.agent0: anorth asouth aeast awest +.agent1: w1north w1south w1east w1west +.agent2: w2north w2south w2east w2west \ No newline at end of file diff --git a/examples/wumpus_game_copy/agent0.ltlf b/examples/wumpus_game_copy/agent0.ltlf new file mode 100644 index 00000000..f7983c55 --- /dev/null +++ b/examples/wumpus_game_copy/agent0.ltlf @@ -0,0 +1 @@ +((E(F(at00 && F(at11)))) && A(G((anord || asud || aovest || aest) && (anord -> (!asud && !aovest && !aest)) && (asud -> (!anord && !aovest && !aest)) && (aovest -> (!anord && !asud && !aest)) && (aest -> (!anord && !asud && !aovest))))) \ No newline at end of file diff --git a/examples/wumpus_game_copy/env.ltlf b/examples/wumpus_game_copy/env.ltlf new file mode 100644 index 00000000..d1bbfb4b --- /dev/null +++ b/examples/wumpus_game_copy/env.ltlf @@ -0,0 +1 @@ +A(G((anord || asud || aovest || aest) && (anord -> (!asud && !aovest && !aest)) && (asud -> (!anord && !aovest && !aest)) && (aovest -> (!anord && !asud && !aest)) && (aest -> (!anord && !asud && !aovest))) -> ((!at00 && !at01 && at10 && !at11) && G(((at00 && anord) -> X(at00 && !at01 && !at10 && !at11)) && ((at00 && asud) -> X(!at00 && !at01 && at10 && !at11)) && ((at00 && aovest) -> X(at00 && !at01 && !at10 && !at11)) && ((at00 && aest) -> X(!at00 && at01 && !at10 && !at11)) && ((at01 && anord) -> X(!at00 && at01 && !at10 && !at11)) && ((at01 && asud) -> X(!at00 && !at01 && !at10 && at11)) && ((at01 && aovest) -> X(at00 && !at01 && !at10 && !at11)) && ((at01 && aest) -> X(!at00 && at01 && !at10 && !at11)) && ((at10 && anord) -> X(at00 && !at01 && !at10 && !at11)) && ((at10 && asud) -> X(!at00 && !at01 && at10 && !at11)) && ((at10 && aovest) -> X(!at00 && !at01 && at10 && !at11)) && ((at10 && aest) -> X(!at00 && !at01 && !at10 && at11)) && ((at11 && anord) -> X(!at00 && at01 && !at10 && !at11)) && ((at11 && asud) -> X(!at00 && !at01 && !at10 && at11)) && ((at11 && aovest) -> X(!at00 && !at01 && at10 && !at11)) && ((at11 && aest) -> X(!at00 && !at01 && !at10 && at11))))) \ No newline at end of file diff --git a/examples/wumpus_game_copy/var.part b/examples/wumpus_game_copy/var.part new file mode 100644 index 00000000..0e03872d --- /dev/null +++ b/examples/wumpus_game_copy/var.part @@ -0,0 +1,2 @@ +.inputs: at00 at01 at10 at11 +.agent0: anord asud aest aovest diff --git a/scripts/deploy-docs.sh b/scripts/deploy-docs.sh old mode 100755 new mode 100644 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 49ef43e6..dfc81955 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,7 +5,7 @@ add_subdirectory(synthesis) include_directories(cli ${UTILS_INCLUDE_PATH} ${PARSER_INCLUDE_PATH} ${SYNTHESIS_INCLUDE_PATH} ${EXT_INCLUDE_PATH}) add_executable(LydiaSyft Main.cpp cli/base.cpp cli/fairness.cpp cli/gr1.cpp cli/maxset.cpp cli/stability.cpp cli/synthesis.cpp) -target_link_libraries(LydiaSyft ${PARSER_LIB_NAME} ${SYNTHESIS_LIB_NAME} ${UTILS_LIB_NAME} ${LYDIA_LIBRARIES}) +target_link_libraries(LydiaSyft ${SYNTHESIS_LIB_NAME} ${PARSER_LIB_NAME} ${UTILS_LIB_NAME} ${LYDIA_LIBRARIES}) install(TARGETS LydiaSyft diff --git a/src/cli/synthesis.cpp b/src/cli/synthesis.cpp index b5833cd9..c5dbf7fd 100644 --- a/src/cli/synthesis.cpp +++ b/src/cli/synthesis.cpp @@ -25,11 +25,11 @@ namespace Syft { total_time_stopwatch.start(); // preprocessing - auto one_step_result = preprocessing(*args_.formula, args_.partition, *var_mgr_, args_.starting_player); - if (handle_preprocessing_result_(one_step_result, total_time_stopwatch)) { - // preprocessing was successful - return; - } + // auto one_step_result = preprocessing(*args_.formula, args_.partition, *var_mgr_, args_.starting_player); + // if (handle_preprocessing_result_(one_step_result, total_time_stopwatch)) { + // // preprocessing was successful + // return; + // } // proceed with DFA construction auto symbolic_dfa = do_dfa_construction_(); diff --git a/src/parser/Parser.cpp b/src/parser/Parser.cpp index f8eb6f5a..aa64a8ae 100644 --- a/src/parser/Parser.cpp +++ b/src/parser/Parser.cpp @@ -46,16 +46,76 @@ namespace Syft { std::vector input_substr; input_substr = split(ins_str_trimmed, ","); parser.input_variables = input_substr; + + std::ifstream in(filename); + if (in.is_open()) { + std::string line; + bool inside_outputs = false; + bool inside_guarantees = false; + + parser.agent_variables.clear(); + parser.agent_formulas.clear(); + while (std::getline(in, line)) { + std::string trimmed = Syft::trim(line); + size_t comment_pos = trimmed.find("//"); + if (comment_pos != std::string::npos) { + trimmed = trimmed.substr(0, comment_pos); + } + trimmed = Syft::trim(trimmed); + if (trimmed.empty()) continue; + // 1.OUTPUTS + if (trimmed.find("OUTPUTS") != std::string::npos) { + inside_outputs = true; + inside_guarantees = false; + continue; + } + + // 2. GUARANTEES + if (trimmed.find("GUARANTEES") != std::string::npos) { + inside_guarantees = true; + inside_outputs = false; + continue; + } + + if (inside_outputs) { + if (trimmed.find("}") != std::string::npos) { inside_outputs = false; continue; } + //Trimm + std::replace(trimmed.begin(), trimmed.end(), ';', ' '); + std::vector vars = split(trimmed, " "); + std::vector clean_vars; + for(auto& v : vars) { + std::string v_c = Syft::trim(v); + if(!v_c.empty() && v_c != "{" && v_c != "}") clean_vars.push_back(v_c); + } + if(!clean_vars.empty()) parser.agent_variables.push_back(clean_vars); + } + else if (inside_guarantees) { + if (trimmed.find("}") != std::string::npos) { inside_guarantees = false; continue; } + size_t semi = trimmed.find(";"); + if (semi != std::string::npos) trimmed = trimmed.substr(0, semi); + std::string clean_f = Syft::trim(trimmed); + + if(!clean_f.empty() && clean_f != "{" && clean_f != "}") { + parser.agent_formulas.push_back(clean_f); + } + } + } + in.close(); + } + - std::string cmd_get_outs = syfco_bin_path + " --format ltlxba-fin --print-output-signals "+filename; - std::string outs_str = parser.exec(cmd_get_outs.c_str()); - std::string outs_str_trimmed = Syft::trim(outs_str); - outs_str_trimmed.erase(std::remove_if(outs_str_trimmed.begin(), outs_str_trimmed.end(), ::isspace), - outs_str_trimmed.end()); - std::vector output_substr; - output_substr = split(outs_str_trimmed, ","); - parser.output_variables = output_substr; + // Fallback: we uso syco standard + if (parser.agent_variables.empty()) { + std::string cmd_get_outs = syfco_bin_path + " --format ltlxba-fin --print-output-signals "+filename; + std::string outs_str = parser.exec(cmd_get_outs.c_str()); + std::string outs_str_trimmed = Syft::trim(outs_str); + outs_str_trimmed.erase(std::remove_if(outs_str_trimmed.begin(), outs_str_trimmed.end(), ::isspace), + outs_str_trimmed.end()); + std::vector single_agent_vars = split(outs_str_trimmed, ","); + parser.agent_variables.push_back(single_agent_vars); + } + std::string cmd_get_target = syfco_bin_path + " --format ltlxba-fin -g "+filename; std::string target_str = parser.exec(cmd_get_target.c_str()); std::string target_str_trimmed = Syft::trim(target_str); @@ -70,16 +130,26 @@ namespace Syft { return input_variables; } - std::vector Parser::get_output_variables() const{ - return output_variables; + std::vector> Parser::get_agent_variables() const{ + return agent_variables; } std::string Parser::get_formula() const{ return formula; } + std::vector Parser::get_agent_formulas() const{ + return agent_formulas; + } + bool Parser::get_sys_first() const{ return sys_first; } + + //backword compatibility + std::vector Parser::get_output_variables() const{ + if(agent_variables.empty()) return {}; + return agent_variables[0]; + } } diff --git a/src/parser/Parser.h b/src/parser/Parser.h index 6d33f1bb..f5bb380e 100644 --- a/src/parser/Parser.h +++ b/src/parser/Parser.h @@ -17,7 +17,8 @@ namespace Syft { private: std::vector input_variables; - std::vector output_variables; + std::vector> agent_variables; + std::vector agent_formulas; std::string formula; bool sys_first; @@ -47,19 +48,32 @@ namespace Syft { std::vector get_input_variables() const; /** - * \brief Return output variables in a vector. + * \brief Return agent variables in a vector. */ - std::vector get_output_variables() const; + std::vector> get_agent_variables() const; /** * \brief Return the formula. */ std::string get_formula() const; + /** + * \brief Return the formula. + */ + std::vector get_agent_formulas() const; + /** * \brief Return true if the target is a Moore machine. */ bool get_sys_first() const; + + + /** + * \brief Return output variables in a vector. + */ + std::vector get_output_variables() const; + + }; } diff --git a/src/synthesis/header/Actor.h b/src/synthesis/header/Actor.h new file mode 100644 index 00000000..d091ebb8 --- /dev/null +++ b/src/synthesis/header/Actor.h @@ -0,0 +1,36 @@ +#ifndef ACTOR_H +#define ACTOR_H + +#include + +namespace Syft { + enum class Role{ + Environment, + MainAgent, + PeerAgent + }; + + class Actor{ + private: + Role role_; + int id_; + public: + Actor(Role role, int id); + + bool is_agent() const; + bool is_environment() const; + + Role role() const; + int id() const; + + bool operator == (const Actor& other) const; + bool operator != (const Actor& other) const; + + static Actor Environment(); + static Actor MainAgent(); + static Actor PeerAgent(int i); + + }; +} + +#endif \ No newline at end of file diff --git a/src/synthesis/header/Synthesizer.h b/src/synthesis/header/Synthesizer.h index 7b48b465..facaf96f 100644 --- a/src/synthesis/header/Synthesizer.h +++ b/src/synthesis/header/Synthesizer.h @@ -4,8 +4,11 @@ #include #include "game/Transducer.h" +#include "game/Transducer_multiagent.h" #include #include +#include "lydia/logic/pnf.hpp" +#include "lydia/logic/pp_pnf.hpp" namespace Syft { @@ -18,6 +21,7 @@ namespace Syft { CUDD::BDD winning_states; CUDD::BDD winning_moves; std::unique_ptr transducer; + std::unique_ptr transducer_multiagent; CUDD::BDD safe_states; }; @@ -31,7 +35,13 @@ namespace Syft { std::optional realizability = std::nullopt; CUDD::BDD winning_move; }; - + + struct LTLfPlus { + std::string color_formula_; + std::unordered_map formula_to_color_; + std::unordered_map formula_to_quantification_; + }; + /** * \brief Abstract class for synthesizers. * @@ -62,6 +72,7 @@ namespace Syft { * a transducer representing a winning strategy for the specification or nullptr if the specification is unrealizable. */ virtual SynthesisResult run() const = 0; + }; } diff --git a/src/synthesis/header/Utils.h b/src/synthesis/header/Utils.h index bed7e2c1..697ee8ce 100644 --- a/src/synthesis/header/Utils.h +++ b/src/synthesis/header/Utils.h @@ -29,9 +29,14 @@ namespace Syft { const whitemech::lydia::ltlf_ptr formula; }; + // Single formula (backward compatibility) TLSFArgs parse_tlsf(const std::shared_ptr &driver, const std::string &formula_file, const std::optional &path_to_syfco_opt = std::nullopt); + // NEW // Multi-agent: multiple formulas + std::vector parse_multi_tlsf(const std::shared_ptr &driver, + const std::vector &formula_files, const std::optional &path_to_syfco_opt = std::nullopt); + std::string find_syfco_path(const std::optional & syfco_path_opt) ; std::string find_binary_path(const std::optional & binary_path_opt, const std::string& executable_name, const std::string& default_value) ; @@ -44,6 +49,10 @@ namespace Syft { Syft::SymbolicStateDfa do_dfa_construction(const whitemech::lydia::LTLfFormula &formula, const std::shared_ptr &var_mgr); + // Multi-agent: multiple DFAs + std::vector + do_multi_dfa_construction(const std::vector &formulas, const std::shared_ptr &var_mgr); + std::string read_assumption_file_if_file_specified(const std::optional &filename); } diff --git a/src/synthesis/header/VarMgr.h b/src/synthesis/header/VarMgr.h index 3488f744..de127abb 100644 --- a/src/synthesis/header/VarMgr.h +++ b/src/synthesis/header/VarMgr.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "cuddObj.hh" @@ -20,8 +21,8 @@ namespace Syft { std::unordered_map name_to_variable_; std::size_t state_variable_count_ = 0; std::vector> state_variables_; - std::vector input_variables_; - std::vector output_variables_; + std::vector input_variables_; //enviroment variables + std::vector> agent_variables_; // 0 is the main agent, 1..n are peer agents std::size_t total_variable_count_ = 0; public: @@ -31,6 +32,16 @@ namespace Syft { */ VarMgr(); + /** + * \brief Prints the VarMgr + */ + void print_mgr() const; + + /** + * \brief Prints BDD variables + */ + void print_bdd_vars(CUDD::BDD cube) const; + /** * \brief Creates BDD variables and associates each with a name. * @@ -39,6 +50,23 @@ namespace Syft { */ void create_named_variables(const std::vector &variable_names); + /** + * \brief Create and store input variables + * + * \param input_vars The input variables to create + * \return void. Adds input_vars to input variables + */ + void create_input_variables(const std::vector& input_vars); + + /** + * \brief Create and store agent variables for a specific agent + * + * \param agent_id The ID of the agent (0 for main agent, 1..n for peer agents) + * \param agent_vars The agent variables to create + * \return void. Adds agent_vars to the specified agent's variables + */ + void create_agent_variables(std::size_t agent_id, const std::vector& agent_vars); + /** * \brief Creates and stores state variables. * @@ -51,6 +79,18 @@ namespace Syft { */ std::size_t create_state_variables(std::size_t variable_count); + /** + * \brief Creates and stores named state variables + * + * Multiple calls of this function create separate groups of state variables. + * The call generates an ID for the automaton whose state space the variables + * represent, so that the correct group of variables can be retrieved later. + * + * \param variable_names The names of the variables being created. + * \return The automaton ID the variables are associated with. + */ + std::size_t create_named_state_variables(const std::vector& variable_names); + /** * \brief Registers a new automaton ID associated with a product state space. * @@ -72,6 +112,14 @@ namespace Syft { */ CUDD::BDD state_variable(std::size_t automaton_id, std::size_t i) const; + /** + * \brief get the state variables of the automaton with the given automaton_id + * + * \param automaton_id. The automaton_id of the DFA whose state variables are being returned + * \return std::vector of state variables + */ + std::vector state_variables(std::size_t automaton_id) const; + /** * \brief Converts a state vector to a BDD. * @@ -86,15 +134,15 @@ namespace Syft { const std::vector &state_vector) const; /** - * \brief Partitions the named variables between inputs and outputs. + * \brief Partitions the named variables between inputs and agents. * * \param input_names The names of the variables that should be considered * inputs. - * \param output_names The names of the variables that should be considered - * outputs. + * \param agent_names A vector of vectors, where agent_names[0] contains + * the names for the main agent, and agent_names[1..n] for peer agents. */ void partition_variables(const std::vector &input_names, - const std::vector &output_names); + const std::vector> &agent_names); /** * \brief Returns the CUDD manager used to create the variables. @@ -111,6 +159,11 @@ namespace Syft { */ std::string index_to_name(int index) const; + /** + * \brief Returns the map between integers and var names + */ + std::unordered_map get_index_to_name_map() const; + /** * \brief Returns the total number of variables, including named and state. */ @@ -132,19 +185,57 @@ namespace Syft { std::size_t input_variable_count() const; /** - * \brief Returns the number of output variables. + * \brief Returns the number of environment variables. + */ + std::size_t environment_variable_count() const; + + /** + * \brief Returns the number of variables for a given agent. */ - std::size_t output_variable_count() const; + std::size_t agent_variable_count(std::size_t agent_id) const; + + /** + * \brief Returns the number agents. + */ + std::size_t agents_count() const; /** - * \brief Returns a BDD formed by the conjunction of all input variables. + * \brief Backword compatibility: Returns a BDD formed by the conjunction of all environment variables. */ CUDD::BDD input_cube() const; /** - * \brief Returns a BDD formed by the conjunction of all output variables. + * \brief Returns a BDD formed by the conjunction of all agents variables. + */ + CUDD::BDD environment_input_cube() const; + + /** + * \brief Returns a BDD formed by the conjunction of all environment variables. + */ + CUDD::BDD environment_output_cube() const; + + + /** + * \brief Returns a BDD formed by the conjunction of all the variables wich are not under the control of agent with ID agent_id. + */ + CUDD::BDD agent_input_cube(std::size_t agent_id) const; + + /** + * \brief Returns a BDD formed by the conjunction of all agent variables for a given agent. */ - CUDD::BDD output_cube() const; + CUDD::BDD agent_output_cube(std::size_t agent_id) const; + + /** + * @brief Determine whether a string is an input variable + * \param var. The name of a variable as a string + */ + bool is_input_variable(const std::string& var) const; + + /** + * @brief Determine whether a string is an agent variable + * \param var. The name of a variable as a string + * \param agent_id. The ID of the agent */ + bool is_agent_variable(const std::string& var, std::size_t agent_id) const; /** * \brief Returns a BDD formed by the conjunction of all state variables of automaton automaton_id. @@ -218,14 +309,15 @@ namespace Syft { std::vector input_variable_labels() const; /** - * \brief Returns a vector with a label for each output variable. + * \brief Returns a vector with a label for each agent variable. * * To be used with CUDD::Cudd::DumpDot. * + * \param agent_id The ID of the agent whose variables to label. * \return A vector \a v such that \a v[i] contains the name of the i-th - * output variable. + * agent variable. */ - std::vector output_variable_labels() const; + std::vector agent_variable_labels(std::size_t agent_id) const; /** * \brief Returns a vector with a label for each state variable. @@ -281,6 +373,44 @@ namespace Syft { */ std::size_t create_complement_state_space( const std::size_t automaton_id); + + /** + * \brief Create the state space when creating the core of a DFA. + * + */ + std::size_t create_core_state_space( + const std::size_t automaton_id, const std::string& sink_name); + + /** + * \brief Create the non empty space. + * + */ + std::size_t create_nonempty_state_space( + const std::size_t automaton_id, const std::string& ne_name); + + + // Backward compatibility for two-player + void create_output_variables(const std::vector& output_vars) { + create_agent_variables(0, output_vars); // Main agent is index 0 + } + std::size_t output_variable_count() const { + return agent_variable_count(0); + } + CUDD::BDD output_cube() const { + return agent_output_cube(0); + } + bool is_output_variable(const std::string& var) const { + return is_agent_variable(var, 0); + } + std::vector output_variable_labels() const { + return agent_variable_labels(0); + } + // Overload partition_variables for old signature + void partition_variables(const std::vector &input_names, + const std::vector &output_names) { + std::vector> agent_names = {output_names}; // Only main agent + partition_variables(input_names, agent_names); + } }; } diff --git a/src/synthesis/header/automata/ExplicitStateDfa.h b/src/synthesis/header/automata/ExplicitStateDfa.h index 924699c8..cd164d63 100644 --- a/src/synthesis/header/automata/ExplicitStateDfa.h +++ b/src/synthesis/header/automata/ExplicitStateDfa.h @@ -86,6 +86,15 @@ namespace Syft { */ static ExplicitStateDfa dfa_of_formula(const whitemech::lydia::LTLfFormula &formula); + /** + * \brief Construct an explicit-state DFA from a given LDLf formula using Lydia. + * + * + * \param formula An LDLf formula. + * \return The corresponding explicit-state DFA. + */ + static ExplicitStateDfa dfa_of_ldlf_formula(const whitemech::lydia::LDLfFormula& formula); + /** * \brief Take the product AND of a sequence of explicit-state DFAs. * @@ -145,6 +154,50 @@ namespace Syft { */ static ExplicitStateDfa dfa_complement(ExplicitStateDfa &d); + //---------------------------------------------------------------------------------------------S + //set of functions taken from LydiaSyft Plus tool, to be used for obligation fragment synthesis + + /** + * \brief Get final states. + */ + std::vector get_final(); + + /** + * \brief Get the initial state. + */ + std::size_t get_initial(); + + // Obligation-fragment specific conversions that preserve original DFA untouched elsewhere. + static ExplicitStateDfa dfa_to_Gdfa_obligation(const ExplicitStateDfa &d); + static ExplicitStateDfa dfa_to_Fdfa_obligation(const ExplicitStateDfa &d); + + /** + * \brief Minimize a deterministic weak automaton using Löding's O(n log n) algorithm. + * + * This function implements the algorithm from: + * Christof Löding, "Efficient minimization of deterministic weak ω-automata" + * Information Processing Letters 79 (2001) 105–109 + * + * A weak automaton is one where each SCC is either entirely accepting or entirely rejecting. + * The algorithm: + * 1. Computes SCCs and maximal coloring in O(n) time + * 2. Normalizes final states based on coloring (even = final) + * 3. Applies standard DFA minimization in O(n log n) time + * + * \param d The weak DFA to be minimized. + * \return The minimal weak automaton recognizing the same ω-language. + */ + static ExplicitStateDfa dfa_minimize_weak(const ExplicitStateDfa &d); + + //verificare se servono + static ExplicitStateDfa dfa_to_Gdfa(ExplicitStateDfa &d); + + static ExplicitStateDfa dfa_to_Fdfa(ExplicitStateDfa &d); + + static ExplicitStateDfa dfa_remove_initial_self_loops(ExplicitStateDfa &d); + + + private: static std::vector traverse_bdd(CUDD::BDD dd, std::shared_ptr var_mgr, std::vector &names, diff --git a/src/synthesis/header/automata/SymbolicStateDfa.h b/src/synthesis/header/automata/SymbolicStateDfa.h index 8dd29e57..6431145b 100644 --- a/src/synthesis/header/automata/SymbolicStateDfa.h +++ b/src/synthesis/header/automata/SymbolicStateDfa.h @@ -7,6 +7,7 @@ #include #include "ExplicitStateDfaAdd.h" +#include "Synthesizer.h" namespace Syft { @@ -47,6 +48,23 @@ namespace Syft { public: + /** + * \brief Constructs a symbolic DFA + * + * \param var_mgr. The variable manager of the DFA + * \param automaton_id. The ID of the DFA within the variable manager + * \param initial_state. The initial state of the DFA + * \param transition_function. The transition function of the DFA + * \param final_states. The final states of the DFA + */ + SymbolicStateDfa( + std::shared_ptr var_mgr, + std::size_t automaton_id, + const std::vector& initial_state, + const std::vector& transition_function, + const CUDD::BDD& final_states + ); + /** * \brief Converts an explicit DFA to a symbolic representation. @@ -180,8 +198,29 @@ namespace Syft { * \return A symbolic DFA of the complement. */ static SymbolicStateDfa complement(const SymbolicStateDfa dfa); + + /** + + /** + * \brief Returns the CORE of a formula. + * + * \param dwa of the specification of an actor. + * \param protagonist_actor the protagonist actor for which we want to compute the CORE. + * \param starting_actor the starting actor for the game used to compute the winning region. + * \return A symbolic DWA that accepts the CORE(formula). + */ + static SymbolicStateDfa get_CORE(const SymbolicStateDfa &dfa, Actor protagonist_actor, Actor starting_actor); + + /** + * \brief Returns a dwa. + * + * \param dwa of the specification of an actor. + * \param protagonist_actor the protagonist actor. + * \return A symbolic DWA with a non empty new state variable. + */ + static SymbolicStateDfa get_NE(const SymbolicStateDfa &dfa, Actor protagonist_actor); }; - + } #endif // SYMBOLIC_STATE_DFA_H diff --git a/src/synthesis/header/game/BuchiReachability_multiagent.hpp b/src/synthesis/header/game/BuchiReachability_multiagent.hpp new file mode 100644 index 00000000..427eb68f --- /dev/null +++ b/src/synthesis/header/game/BuchiReachability_multiagent.hpp @@ -0,0 +1,57 @@ +// +// Created by shuzhu on 16/04/24. +// + +#ifndef LYDIASYFT_BUCHIREACHABILITY_MULTIAGENT_HPP +#define LYDIASYFT_BUCHIREACHABILITY_MULTIAGENT_HPP + +#include "game/DfaGameSynthesizer_multiagent.h" + +namespace Syft { +/** + * \brief A single-strategy-synthesizer for a Buchi-reachability game given as a symbolic-state DFA. + * + * Either Buchi condition holds or reachability condition holds. + */ + class BuchiReachability_multiagent : public DfaGameSynthesizer_multiagent { + private: + /** + * \brief The set of goal states. + */ + CUDD::BDD Buchi_; //states to visit inifinitely often + /** + * \brief The state space to consider. + */ + CUDD::BDD state_space_; + + public: + + /** + * \brief Construct a single-strategy-synthesizer for the given Buchi-reachability game. + * + * \param spec A symbolic-state DFA representing the Buchi-reachability game arena. + * \param starting_actor The player that moves first each turn. + * \param protagonist_actor The player for which we aim to find the winning strategy. + * \param Buchi The Buchi condition represented as a Boolean formula \beta over input variables, denoting the Buchi condition FG\beta. + * \param state_space The state space. + * \param num_agents number of agents + */ + BuchiReachability_multiagent(const SymbolicStateDfa &spec, Actor starting_actor, Actor protagonist_actor, + const CUDD::BDD &Buchi, const CUDD::BDD &state_space, size_t num_agents); + + + /** + * \brief Solves the Buchi-reachability game. + * + * \return The result consists of + * realizability + * a set of agent winning states + * a transducer representing a winning strategy or nullptr if the game is unrealizable. + */ + SynthesisResult run() const final; + + }; +} + + +#endif //LYDIASYFT_BUCHIREACHABILITY_HPP diff --git a/src/synthesis/header/game/DfaGameSynthesizer_multiagent.h b/src/synthesis/header/game/DfaGameSynthesizer_multiagent.h new file mode 100644 index 00000000..b66e4688 --- /dev/null +++ b/src/synthesis/header/game/DfaGameSynthesizer_multiagent.h @@ -0,0 +1,122 @@ +#ifndef DFA_GAME_SYNTHESIZER_MULTIAGENT_H +#define DFA_GAME_SYNTHESIZER_MULTIAGENT_H + +#include "Quantification.h" +#include "automata/SymbolicStateDfa.h" +#include "Synthesizer.h" +#include "Actor.h" +#include "Transducer_multiagent.h" + +namespace Syft { + + +/** + * \brief A synthesizer for a game whose arena is a symbolic-state DFA. + */ + class DfaGameSynthesizer_multiagent : public Synthesizer { + protected: + /** + * \brief number of agents. + */ + size_t num_total_agents_; + /** + * \brief Variable manager. + */ + std::shared_ptr var_mgr_; + /** + * \brief The player that moves first each turn. + */ + Actor starting_actor_; + /** + * \brief The player for which we aim to find the winning strategy. + */ + Actor protagonist_actor_; + /** + * \brief The initial state of the game arena. + */ + std::vector initial_vector_; + /** + * \brief The transition function of the game arena. + */ + std::vector transition_vector_; + /** + * \brief Quantification on the variables that the protagonist player does not depend on. + */ + std::unique_ptr quantify_independent_variables_; + /** + * \brief Quantification on non-state variables. + */ + std::unique_ptr quantify_non_state_variables_; + + /** + * \brief Compute a set of winning moves. + * + * Basically first collect the transitions that move into a winning state, and then quantify all variables that the protagonist player doesn't depend on. + * \param winning_states A set of winning states. + * \return The preimage. + */ + CUDD::BDD preimage(const CUDD::BDD &winning_states) const; + + /** + * \brief Project a set of winning moves to a set of winning states. + * + * Basically quantify all the non-state variables. + * \param winning_moves A set of winning moves. + * \return A set of winning states. + */ + CUDD::BDD project_into_states(const CUDD::BDD &winning_moves) const; + + /** + * \brief Check whether the initial state is a winning state. + * + * \param winning_states A set of winning states. + * \return True if the initial state is a winning state. + */ + bool includes_initial_state(const CUDD::BDD &winning_states) const; + + public: + + /** + * \brief Construct a synthesizer for a given DFA game. + * + * The winning condition is unspecified and should be defined by the subclass. + * + * \param spec A symbolic-state DFA representing the game's arena. + * \param starting_player The player that moves first each turn. + * \param protagonist_player The player for which we aim to find the winning strategy. + * \param num_agents The number of agents in the game. + */ + DfaGameSynthesizer_multiagent(SymbolicStateDfa spec, Actor starting_actor, Actor protagonist_actor, size_t num_agents); + + + /** + * \brief Synthesis for the game. + * + * \return The synthesis result, consisting of realizability, the set of winning states and the set of winning moves. + */ + virtual SynthesisResult run() + const override = 0; + + + /** + * \brief Abstract a winning strategy for the game. + * + * \return A winning strategy represented as a transducer. + */ + std::unique_ptr AbstractSingleStrategy(const SynthesisResult &result) const; + + private: + std::unique_ptr abstract_single_strategy(const CUDD::BDD &winning_moves, + const std::shared_ptr &var_mgr, + const std::vector &initial_vector, + const std::vector &transition_vector, + Actor starting_actor, + Actor protagonist_actor) const; + + std::unordered_map + synthesize_strategy(const CUDD::BDD &winning_moves, const std::shared_ptr &var_mgr) const; + }; + +} + +#endif // DFA_GAME_SYNTHESIZER_H diff --git a/src/synthesis/header/game/InputOutputPartition.h b/src/synthesis/header/game/InputOutputPartition.h index dd31f368..cd5b8b08 100644 --- a/src/synthesis/header/game/InputOutputPartition.h +++ b/src/synthesis/header/game/InputOutputPartition.h @@ -7,7 +7,7 @@ namespace Syft { /** - * \brief A partition of variables into input and output variables. + * \brief A partition of variables into input and agent variables. */ class InputOutputPartition { private: @@ -17,7 +17,7 @@ class InputOutputPartition { public: std::vector input_variables; - std::vector output_variables; + std::vector> agent_variables; // agent_variables[0] is main agent, 1..n are peer agents /** * \brief Creates a partition with no variables. @@ -30,16 +30,17 @@ class InputOutputPartition { bool is_input(const std::string& var_name); /** - * \brief check if a variable is an output variable + * \brief check if a variable is an agent variable */ - bool is_output(const std::string& var_name); + bool is_agent(const std::string& var_name, std::size_t agent_id); /** * \brief Constructs a partition from a file. * * The file should look like * .inputs: X1 X2 X3 X4 - * .outputs: Y1 Y2 Y3 + * .agent0: Y1 Y2 Y3 # main agent + * .agent1: Z1 Z2 # peer agent 1 * * \param filename The name of the partition file. * \return A partition with the input and output variables listed in the file @@ -51,10 +52,23 @@ class InputOutputPartition { * * * \param inputs_substr A string vector of input variables. - * \param outputs_substr A string vector of output variables. + * \param agents_substr A vector of string vectors, one for each agent. * \return A partition with the input and output variables listed in the file */ - static InputOutputPartition construct_from_input(const std::vector inputs_substr, std::vector outputs_substr); + static InputOutputPartition construct_from_input(const std::vector inputs_substr, const std::vector> agents_substr); + + // Backward compatibility + std::vector output_variables; // Alias for agent_variables[0] + bool is_output(const std::string& var_name) { + return is_agent(var_name, 0); + } + static InputOutputPartition construct_from_input(const std::vector inputs_substr, + const std::vector outputs_substr) { + std::vector> agents = {outputs_substr}; + auto part = construct_from_input(inputs_substr, agents); + part.output_variables = outputs_substr; // Set alias + return part; + } }; } diff --git a/src/synthesis/header/game/Reachability_multiagent.hpp b/src/synthesis/header/game/Reachability_multiagent.hpp new file mode 100644 index 00000000..1e6b3bf0 --- /dev/null +++ b/src/synthesis/header/game/Reachability_multiagent.hpp @@ -0,0 +1,58 @@ +// +// Created by shuzhu on 16/04/24. +// + +#ifndef LYDIASYFT_REACHABILITY_MULTIAGENT_HPP +#define LYDIASYFT_REACHABILITY_MULTIAGENT_HPP + +#include "game/DfaGameSynthesizer_multiagent.h" +#include "Actor.h" + +namespace Syft { +/** + * \brief A single-strategy-synthesizer for a reachability game given as a symbolic-state DFA. + * + * Reachability condition holds. + */ + class Reachability_multiagent : public DfaGameSynthesizer_multiagent { + private: + /** + * \brief The set of goal states. + */ + CUDD::BDD goal_states_; + /** + * \brief The state space to consider. + */ + CUDD::BDD state_space_; + + public: + + /** + * \brief Construct a single-strategy-synthesizer for the given reachability game. + * + * \param spec A symbolic-state DFA representing the reachability game arena. + * \param starting_actor The actor that moves first each turn. + * \param protagonist_actor The actor for which we aim to find the winning strategy. + * \param goal_states The reachability condition. + * \param state_space The state space. + * \param agent_variables The variables corresponding to each agent, used for quantification in the preimage computation. + */ + Reachability_multiagent(const SymbolicStateDfa &spec, Actor starting_actor, Actor protagonist_actor, + const CUDD::BDD &goal_states, const CUDD::BDD &state_space, size_t num_agents); + + + /** + * \brief Solves the reachability game. + * + * \return The result consists of + * realizability + * a set of agent winning states + * a transducer representing a winning strategy or nullptr if the game is unrealizable. + */ + SynthesisResult run() const final; + + }; +} + + +#endif //LYDIASYFT_REACHABILITY_HPP diff --git a/src/synthesis/header/game/Transducer.h b/src/synthesis/header/game/Transducer.h index 546b80e7..f6614a8e 100644 --- a/src/synthesis/header/game/Transducer.h +++ b/src/synthesis/header/game/Transducer.h @@ -40,6 +40,10 @@ class Transducer { * \brief Saves the output function of the transducer in a .dot file. */ void dump_dot(const std::string& filename) const; + + std::unordered_map get_output_function() const { + return output_function_; + } }; } diff --git a/src/synthesis/header/game/Transducer_multiagent.h b/src/synthesis/header/game/Transducer_multiagent.h new file mode 100644 index 00000000..32712fd0 --- /dev/null +++ b/src/synthesis/header/game/Transducer_multiagent.h @@ -0,0 +1,61 @@ +#ifndef TRANSDUCER_MULTIAGENT_H +#define TRANSDUCER_MULTIAGENT_H + +#include +#include +#include + +#include + +#include "Actor.h" +#include "VarMgr.h" + +namespace Syft { + +/** + * \brief A symbolic tranducer representing a winning strategy for a game. + * + * May be either a Moore or Mealy machine. + */ +class Transducer_multiagent { + private: + + const std::shared_ptr var_mgr_; + const std::vector initial_vector_; + const std::unordered_map output_function_; + const std::vector transition_function_; + const Actor starting_actor_; + const Actor protagonist_actor_; + + public: + + Transducer_multiagent(const std::shared_ptr& var_mgr, + const std::vector& initial_vector, + const std::unordered_map& output_function, + const std::vector& transition_function, + Actor starting_actor, + Actor protagonist_actor); + + /** + * \brief Saves the output function of the transducer in a .dot file. + */ + void dump_dot(const std::string& filename) const; + + std::unordered_map get_output_function() const { + return output_function_; + } + + std::vector get_transition_function() const { + return transition_function_; + } + std::vector get_initial_vector() const { + return initial_vector_; + } + const std::unordered_map& output_function() const { + return output_function_; + } +}; + +} + +#endif // TRANSDUCER_MULTIAGENT_H diff --git a/src/synthesis/header/synthesizer/ObligationLTLfPlusSynthesizer.h b/src/synthesis/header/synthesizer/ObligationLTLfPlusSynthesizer.h new file mode 100644 index 00000000..51ea5fb5 --- /dev/null +++ b/src/synthesis/header/synthesizer/ObligationLTLfPlusSynthesizer.h @@ -0,0 +1,174 @@ +#ifndef OBLIGATION_LTLF_PLUS_SYNTHESIZER_H +#define OBLIGATION_LTLF_PLUS_SYNTHESIZER_H + +#include "automata/SymbolicStateDfa.h" +#include "automata/ExplicitStateDfa.h" +//#include "game/BuchiSolver.hpp" +#include "game/InputOutputPartition.h" +#include "lydia/logic/ltlfplus/base.hpp" +#include "automata/SymbolicStateDfa.h" +#include "Synthesizer.h" +#include "game/InputOutputPartition.h" +#include "lydia/parser/ppltl/driver.hpp" + +#include +#include +#include +#include + +struct MinimisationOptions { + bool allow_minimisation = true; + int threshold = 128; // By default, only minimise small weak automata + int symbolic_threshold = 128; +}; + +namespace CUDD { + // forward-declare minimal wrapper type used in signatures + class BDD; +} + +namespace Syft { + + // Forward declarations for types used by the synthesizer interface. + // These are declared elsewhere in your codebase. + class VarMgr; + struct ELSynthesisResult; + struct SynthesisResult; + + /** + * \brief Synthesizer for LTLf+ formulas in the obligation fragment. + * + * This synthesizer is optimized for the obligation fragment, which consists + * only of safety (forall) and guarantee (exists) quantifiers. + * + */ + class ObligationLTLfPlusSynthesizer { + public: + /** + * Construct an ObligationLTLfPlusSynthesizer. + * + * \param ltlf_plus_formula parsed LTLf+ spec (contains formula->color/quantification) + * \param partition input/output partition (variable names) + * \param starting_actor who moves first each turn + * \param protagonist_actor the actor we synthesise for (Agent/Environment) + * \param allow_minimisation if true, allows minimisation of intermediate DFAs to save memory + */ + ObligationLTLfPlusSynthesizer( + LTLfPlus ltlf_plus_formula, + InputOutputPartition partition, + Actor starting_actor, + Actor protagonist_actor, + //MinimisationOptions minimisation_options = MinimisationOptions(), + std::shared_ptr var_mgr, + bool use_balanced_boolean_product = true + ); + + /** + * Run the synthesizer. + * + * \return result in ELSynthesisResult format. + */ + ELSynthesisResult run() const; + + /** + * \brief If set to true, run() will build the automata arena but skip + * the game-solving step and return an empty result. + * Useful for benchmarking automata construction time only. + */ + void set_skip_synthesis(bool skip) { skip_synthesis_ = skip; } + + /** + * \brief Set a file path to dump the arena DFA in PyDFA format. + * Works with both normal synthesis and --skip-synthesis mode. + */ + void set_dfa_dump_path(const std::string& path) { dfa_dump_path_ = path; } + + + + //QUESTE TRE FUNZIONI ERANO PRIVATE PRIMA + /** + * Parse a boolean color formula and build the arena using a hybrid approach. + * Starts with explicit MONA DFAs for efficiency, but automatically switches + * to symbolic representation when the state space exceeds a threshold (256 states). + * This prevents memory blowup on large products while maintaining MONA efficiency + * for smaller intermediate results. + * + * Returns a SymbolicStateDfa representing the complete product arena. + */ + SymbolicStateDfa build_arena_from_color_formula_hybrid( + const std::string& color_formula, + const std::map& color_to_dfa) const; + + // --- core phases --- + void validate_obligation_fragment() const; + + /** + * \brief Builds the per-color DFAs, combines them according to the color + * formula and returns the arena and a map of per-color finals. + * + * The returned map has per-color final BDDs under their color key and + * the combined/evaluated final-states of the whole arena under key -1. + */ + std::pair> + convert_to_symbolic_dfa() const; + + + + private: + // --- state --- + std::shared_ptr var_mgr_; + LTLfPlus ltlf_plus_formula_; + Actor starting_actor_; + Actor protagonist_actor_; + MinimisationOptions minimisation_options_ = MinimisationOptions(); + bool use_balanced_boolean_product_ = true; + mutable bool skip_synthesis_ = false; + std::string dfa_dump_path_; ///< If non-empty, dump arena DFA in PyDFA format to this file + + + /** + * \brief Solve the synthesis problem by running the Büchi solver on the arena. + * + * The arena is expected to already encode the correct accepting states + * (i.e., arena.final_states() matches the boolean color formula). + * + * \param arena The product arena built from color DFAs. + * \param color_to_final_states mapping from color -> final-states BDD (key -1 holds arena finals). + */ + //ELSynthesisResult solve_with_scc(const SymbolicStateDfa& arena, + // const std::map& color_to_final_states) const; + + /** + * + * Run the Büchi-based solver on the given arena. This is provided + * separately from the SCC-based weak-game solver so callers can + * choose the algorithm at runtime. + */ + //ELSynthesisResult solve_with_buchi(const SymbolicStateDfa& arena, + // const std::map& color_to_final_states) const; + + // --- helpers exposed because they're implemented in the .cpp --- + /** + * Parse a boolean color formula string like "(1 & 2) | 3" and build an explicit + * DFA product using MONA's dfaProduct where numeric tokens reference ExplicitStateDfa + * provided in color_to_dfa. + * + * This computes the product at the MONA level before conversion to symbolic representation. + */ + /* ExplicitStateDfa build_explicit_arena_from_color_formula( + const std::string& color_formula, + const std::map& color_to_dfa) const; + */ + /** + * (Optional) Evaluate a boolean color formula by substituting color integers + * with given BDDs and computing the resulting BDD. Useful if you prefer to + * compute the accepting set directly rather than relying on arena.final_states(). + */ + CUDD::BDD evaluate_color_formula_with_bdds( + const std::string& color_formula, + const std::map& color_to_bdd) const; + }; + +} // namespace Syft + +#endif // OBLIGATION_LTLF_PLUS_SYNTHESIZER_H diff --git a/src/synthesis/source/Actor.cpp b/src/synthesis/source/Actor.cpp new file mode 100644 index 00000000..2e25baf0 --- /dev/null +++ b/src/synthesis/source/Actor.cpp @@ -0,0 +1,29 @@ +#include "Actor.h" + +namespace Syft { + +Actor::Actor(Role role, int id) : role_(role), id_(id) {} + +bool Actor::is_agent() const { + return role_ == Role::MainAgent || role_ == Role::PeerAgent; +} + +bool Actor::is_environment() const { + return role_ == Role::Environment; +} +Role Actor::role() const { return role_;} +int Actor::id() const { return id_;} + +bool Actor::operator==(const Actor &other) const { + return role_ == other.role_ && id_ == other.id_; +} + +bool Actor::operator!=(const Actor &other) const { + return !(*this == other); +} + +Actor Actor::Environment() { return Actor(Role::Environment, -1);} +Actor Actor::MainAgent() { return Actor(Role::MainAgent, 0);} +Actor Actor::PeerAgent(int i) { return Actor(Role::PeerAgent, i);} + +} \ No newline at end of file diff --git a/src/synthesis/source/OneStepRealizability.cpp b/src/synthesis/source/OneStepRealizability.cpp index 96f8030a..36554042 100644 --- a/src/synthesis/source/OneStepRealizability.cpp +++ b/src/synthesis/source/OneStepRealizability.cpp @@ -2,7 +2,7 @@ #include "Synthesizer.h" #include - +#if 0 namespace Syft { void SmtOneStepRealizabilityVisitor::visit(const whitemech::lydia::LTLfTrue &formula) { @@ -124,4 +124,5 @@ namespace Syft { return move; } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/synthesis/source/OneStepUnrealizability.cpp b/src/synthesis/source/OneStepUnrealizability.cpp index 57489d70..1004c772 100644 --- a/src/synthesis/source/OneStepUnrealizability.cpp +++ b/src/synthesis/source/OneStepUnrealizability.cpp @@ -3,7 +3,7 @@ #include "lydia/logic/nnf.hpp" #include - +#if 0 namespace Syft { void SmtOneStepUnrealizabilityVisitor::visit(const whitemech::lydia::LTLfTrue &formula) { @@ -146,4 +146,5 @@ namespace Syft { return false; } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/synthesis/source/Preprocessing.cpp b/src/synthesis/source/Preprocessing.cpp index d4c5e151..199aaf60 100644 --- a/src/synthesis/source/Preprocessing.cpp +++ b/src/synthesis/source/Preprocessing.cpp @@ -12,20 +12,20 @@ namespace Syft { Syft::OneStepSynthesisResult result; // one-step realizability check - auto one_step_realizability_check_result = one_step_realizable(formula, partition, var_mgr); - if (one_step_realizability_check_result.has_value()) { - result.realizability = true; - result.winning_move = one_step_realizability_check_result.value(); - return result; - } + // auto one_step_realizability_check_result = one_step_realizable(formula, partition, var_mgr); + // if (one_step_realizability_check_result.has_value()) { + // result.realizability = true; + // result.winning_move = one_step_realizability_check_result.value(); + // return result; + // } // one-step unrealizability check - bool one_step_unrealizability_check_result = one_step_unrealizable(formula, partition, var_mgr, - starting_player); - if (one_step_unrealizability_check_result) { - result.realizability = false; - return result; - } + // bool one_step_unrealizability_check_result = one_step_unrealizable(formula, partition, var_mgr, + // starting_player); + // if (one_step_unrealizability_check_result) { + // result.realizability = false; + // return result; + // } // preprocessing failed result.realizability = std::nullopt; diff --git a/src/synthesis/source/Utils.cpp b/src/synthesis/source/Utils.cpp index 2cfaed14..e703c1e1 100644 --- a/src/synthesis/source/Utils.cpp +++ b/src/synthesis/source/Utils.cpp @@ -57,7 +57,7 @@ namespace Syft { Syft::InputOutputPartition partition = Syft::InputOutputPartition::construct_from_input(parser.get_input_variables(), - parser.get_output_variables()); + parser.get_agent_variables()); // Parsing the formula whitemech::lydia::ltlf_ptr parsed_formula = parse_formula(driver, parser.get_formula()); @@ -65,13 +65,24 @@ namespace Syft { return {starting_player, protagonist_player, partition, parsed_formula}; } + // Multi-agent version + std::vector parse_multi_tlsf(const std::shared_ptr &driver, + const std::vector &formula_files, const std::optional &path_to_syfco_opt) { + std::vector results; + for (const auto& file : formula_files) { + results.push_back(parse_tlsf(driver, file, path_to_syfco_opt)); + } + return results; + } + whitemech::lydia::ltlf_ptr parse_formula(const std::shared_ptr &driver, const std::string &formula) { std::stringstream formula_stream(formula); driver->parse(formula_stream); - whitemech::lydia::ltlf_ptr parsed_formula = driver->get_result(); + auto result = driver->get_result(); + whitemech::lydia::ltlf_ptr parsed_formula = std::dynamic_pointer_cast(result); // Apply no-empty semantics auto context = driver->context; auto not_end = context->makeLtlfNotEnd(); @@ -94,6 +105,16 @@ namespace Syft { return symbolic_dfa; } + // Multi-agent version + std::vector + do_multi_dfa_construction(const std::vector &formulas, const std::shared_ptr &var_mgr) { + std::vector dfas; + for (const auto& formula : formulas) { + dfas.push_back(do_dfa_construction(formula, var_mgr)); + } + return dfas; + } + std::string read_assumption_file_if_file_specified(const std::optional &filename) { if (!filename.has_value()) { @@ -108,4 +129,4 @@ namespace Syft { return assumption_str; } -} \ No newline at end of file +} diff --git a/src/synthesis/source/VarMgr.cpp b/src/synthesis/source/VarMgr.cpp index f96111b4..47030036 100644 --- a/src/synthesis/source/VarMgr.cpp +++ b/src/synthesis/source/VarMgr.cpp @@ -11,6 +11,80 @@ VarMgr::VarMgr() { mgr_ = std::make_shared(); } +void VarMgr::print_mgr() const { + // prints the number of managed automata + std::cout << "Number of managed automata: " << state_variables_.size() << std::endl; + + // prints number of vars + std::cout << "Number of variables: " << total_variable_count() << std::endl; + + // prints named variables + std::cout << "Named variables (name, var): " << std::endl; + + for (const auto& name_var : name_to_variable_) + std::cout << "Name: " << name_var.first << ". Var: " << name_var.second << std::endl; + + std::cout << "Var indexes (index, name): " << std::endl; + + for (const auto& index_name: index_to_name_) + std::cout << "Index: " << index_name.first << ". Name: " << index_name.second << std::endl; + + // prints X vars + std::cout << "Input variables: " << std::endl; + for (const auto& var: input_variables_) std::cout << "Var: " << var << std::endl; + + // prints Agent vars + for (std::size_t i = 0; i < agent_variables_.size(); ++i) { + std::cout << "Agent " << i << " variables: " << std::endl; + for (const auto& var: agent_variables_[i]) std::cout << "Var: " << var << std::endl; + } + + std::cout << "\nCUBE TEST \n" << std::endl; + std::cout << "---Environment role---" << std::endl; + CUDD::BDD env_out = environment_output_cube(); + CUDD::BDD env_in = environment_input_cube(); + std::cout << "Env Output Cube controls: " << std::endl; + print_bdd_vars(env_out); + std::cout << "Env Input Cube see others moves: " << std::endl; + print_bdd_vars(env_in); + + for(std::size_t i=0; i< agent_variables_.size(); ++i){ + std::cout << "\n---Agent "<< i << " role---"< indices = cube.SupportIndices(); + std::cout << "{"; + for(unsigned int idx : indices){ + auto it= index_to_name_.find(idx); + if(it != index_to_name_.end()){ + std::cout << it->second << "(v" << idx <<") "; + }else{ + std::cout << "v" << idx << " "; + } + } + std::cout << "} (Tot: " << indices.size() << ")" << std::endl; +} + void VarMgr::create_named_variables( const std::vector& variable_names) { for (const std::string& name : variable_names) { @@ -24,6 +98,25 @@ void VarMgr::create_named_variables( } } +void VarMgr::create_input_variables( + const std::vector& input_vars +) { + for (const std::string& input_var : input_vars) { + input_variables_.push_back(name_to_variable_[input_var]); + } +} + +//Create Agent vars +void VarMgr::create_agent_variables(std::size_t agent_id, const std::vector& agent_vars) { + // Ensure agent_variables_ has enough space + if (agent_variables_.size() <= agent_id) { + agent_variables_.resize(agent_id + 1); + } + for (const std::string& agent_var : agent_vars) { + agent_variables_[agent_id].push_back(name_to_variable_[agent_var]); + } +} + std::size_t VarMgr::create_state_variables(std::size_t variable_count) { std::size_t automaton_id = state_variables_.size(); @@ -48,6 +141,33 @@ std::size_t VarMgr::create_state_variables(std::size_t variable_count) { return automaton_id; } +std::size_t VarMgr::create_named_state_variables(const std::vector& vars) { + std::size_t automaton_id = state_variables_.size(); + + // Creates an additional space for variables at index automaton_id, + // then reserves enough memory for all the new variables + state_variables_.emplace_back(); + state_variables_[automaton_id].reserve(vars.size()); + int new_state_variable_counter = 0; + + for (int i = 0; i < vars.size(); ++i) { + // Create a new variable if this named variable does not already exist + // add the new variable to the state variables of the automaton + if (name_to_variable_.find(vars[i]) == name_to_variable_.end()) { + CUDD::BDD new_state_variable = mgr_->bddNewVarAtLevel(0); + state_variables_[automaton_id].push_back(new_state_variable); + name_to_variable_[vars[i]] = new_state_variable; + index_to_name_[new_state_variable.NodeReadIndex()] = vars[i]; + new_state_variable_counter++; + } else { // Else add the existing variable to the state variables of the automaton + state_variables_[automaton_id].push_back(name_to_variable_[vars[i]]); + } + } + + state_variable_count_ += new_state_variable_counter; + return automaton_id; +} + std::size_t VarMgr::create_product_state_space( const std::vector& automaton_ids) { std::size_t product_automaton_id = state_variables_.size(); @@ -78,10 +198,83 @@ std::size_t VarMgr::create_product_state_space( return complement_automaton_id; } + std::size_t VarMgr::create_core_state_space(const std::size_t automaton_id, const std::string& sink_name) { + std::size_t core_automaton_id = state_variables_.size(); + + state_variables_.emplace_back(); + + state_variables_[core_automaton_id].insert( + state_variables_[core_automaton_id].end(), + state_variables_[automaton_id].begin(), + state_variables_[automaton_id].end()); + + for (const CUDD::BDD& var : state_variables_[automaton_id]) { + std::size_t index = var.NodeReadIndex(); + if (index_to_name_.find(index) != index_to_name_.end()) { + index_to_name_[index] = index_to_name_[index]; + } + } + + //Create sink state variable for the core space + std::string unique_sink_name = sink_name + "_" + std::to_string(core_automaton_id); + + // Alloca ed inserisce la nuova variabile di stato (z_sink) per questo core space + CUDD::BDD new_state_variable = mgr_->bddNewVarAtLevel(0); + state_variables_[core_automaton_id].push_back(new_state_variable); + name_to_variable_[unique_sink_name] = new_state_variable; + index_to_name_[new_state_variable.NodeReadIndex()] = unique_sink_name; + + // Aggiorna il contatore globale delle variabili del manager + state_variable_count_ += 1; + + + return core_automaton_id; +} + + + std::size_t VarMgr::create_nonempty_state_space(const std::size_t automaton_id, const std::string& ne_name) { + std::size_t core_automaton_id = state_variables_.size(); + + state_variables_.emplace_back(); + + state_variables_[core_automaton_id].insert( + state_variables_[core_automaton_id].end(), + state_variables_[automaton_id].begin(), + state_variables_[automaton_id].end()); + + for (const CUDD::BDD& var : state_variables_[automaton_id]) { + std::size_t index = var.NodeReadIndex(); + if (index_to_name_.find(index) != index_to_name_.end()) { + index_to_name_[index] = index_to_name_[index]; + } + } + + //Create sink state variable for the core space + std::string unique_ne_name = ne_name + "_" + std::to_string(core_automaton_id); + + // Alloca ed inserisce la nuova variabile di stato (z_sink) per questo core space + CUDD::BDD new_state_variable = mgr_->bddNewVarAtLevel(0); + state_variables_[core_automaton_id].push_back(new_state_variable); + name_to_variable_[unique_ne_name] = new_state_variable; + index_to_name_[new_state_variable.NodeReadIndex()] = unique_ne_name; + + // Aggiorna il contatore globale delle variabili del manager + state_variable_count_ += 1; + + + return core_automaton_id; +} + + + CUDD::BDD VarMgr::state_variable(std::size_t automaton_id, std::size_t i) const { return state_variables_[automaton_id][i]; } + +std::vector VarMgr::state_variables(std::size_t automaton_id) const { + return state_variables_[automaton_id]; +} CUDD::BDD VarMgr::state_vector_to_bdd(std::size_t automaton_id, const std::vector& state_vector) @@ -100,29 +293,48 @@ CUDD::BDD VarMgr::state_vector_to_bdd(std::size_t automaton_id, } void VarMgr::partition_variables(const std::vector& input_names, - const std::vector& output_names) { - if (!input_variables_.empty() || !output_variables_.empty()) { - throw std::runtime_error( - "Error: Only one input-output partition is allowed."); + const std::vector>& agent_names) { + //if (!input_variables_.empty() || !agent_variables_.empty()) { + // throw std::runtime_error( + // "Error: Only one input-agent partition is allowed."); + // } + input_variables_.clear(); + agent_variables_.clear(); + + for(const std::string& input_name : input_names) { + if (name_to_variable_.find(input_name) != name_to_variable_.end()) { + input_variables_.push_back(name_to_variable_[input_name]); + } } -// if (input_names.size() + output_names.size() != index_to_name_.size()) { -// throw std::runtime_error( -// "Error: Input-output partition is the wrong size."); -// } + for(std::size_t i = 0; i < agent_names.size(); ++i) { + agent_variables_.emplace_back(); + for(const std::string& agent_name : agent_names[i]) { + if (name_to_variable_.find(agent_name) != name_to_variable_.end()) { + agent_variables_[i].push_back(name_to_variable_[agent_name]); + } + } + } + std::size_t total_agent_vars = 0; + for (const auto& names : agent_names) { + total_agent_vars += names.size(); + } - if (input_names.size() + output_names.size() != (index_to_name_.size() - state_variable_count_)) { - throw std::runtime_error( - "Error: Input-output partition is the wrong size."); - } - - for (const std::string& input_name : input_names) { - input_variables_.push_back(name_to_variable(input_name)); + if (input_names.size() + total_agent_vars != (index_to_name_.size() - state_variable_count_)) { + throw std::runtime_error( + "Error: Input-agent partition is the wrong size."); } - for (const std::string& output_name : output_names) { - output_variables_.push_back(name_to_variable(output_name)); + + std::size_t actual_partition_size = input_names.size() + total_agent_vars; + std::size_t expected_partition_size = index_to_name_.size() - state_variable_count_; + if (actual_partition_size != expected_partition_size) { + std::string error_msg = "Partition Error:"; + error_msg += " Expected partition size: " + std::to_string(expected_partition_size) + "."; + error_msg += " Actual partition size: " + std::to_string(actual_partition_size) + "."; + throw std::runtime_error(error_msg); } + } std::shared_ptr VarMgr::cudd_mgr() const { @@ -137,8 +349,12 @@ std::string VarMgr::index_to_name(int index) const { return index_to_name_.at(index); } +std::unordered_map VarMgr::get_index_to_name_map() const { + return index_to_name_; +} + std::size_t VarMgr::total_variable_count() const { - return name_to_variable_.size() + total_state_variable_count(); + return name_to_variable_.size(); } std::size_t VarMgr::total_state_variable_count() const { @@ -153,16 +369,65 @@ std::size_t VarMgr::input_variable_count() const { return input_variables_.size(); } -std::size_t VarMgr::output_variable_count() const { - return output_variables_.size(); +std::size_t VarMgr::environment_variable_count() const { + return input_variables_.size(); +} + +std::size_t VarMgr::agent_variable_count(std::size_t agent_id) const { + if (agent_id >= agent_variables_.size()) return 0; + return agent_variables_[agent_id].size(); +} + +std::size_t VarMgr::agents_count() const { + return agent_variables_.size(); +} + +// all agents variables +CUDD::BDD VarMgr::environment_input_cube() const { + CUDD::BDD all_agents_cube = mgr_->bddOne(); + for(std::size_t i = 0; i < agent_variables_.size(); ++i){ + all_agents_cube &= agent_output_cube(i); + } + return all_agents_cube; +} + +CUDD::BDD VarMgr::environment_output_cube() const { + return mgr_->computeCube(input_variables_); } +//env variables + all agents variables, except agent with agent_id ones +CUDD::BDD VarMgr::agent_input_cube(std::size_t agent_id)const{ + CUDD::BDD combined_input = environment_output_cube(); + for (std::size_t i = 0; i < agent_variables_.size(); ++i){ + if((int)i == agent_id) continue; + combined_input &= agent_output_cube(i); + } + return combined_input; +} + +CUDD::BDD VarMgr::agent_output_cube(std::size_t agent_id) const { + if (agent_id >= agent_variables_.size()) return mgr_->bddOne(); + return mgr_->computeCube(agent_variables_[agent_id]); +} + +//Backword compatibility CUDD::BDD VarMgr::input_cube() const { return mgr_->computeCube(input_variables_); } -CUDD::BDD VarMgr::output_cube() const { - return mgr_->computeCube(output_variables_); +bool VarMgr::is_input_variable(const std::string& var) const { + CUDD::BDD bdd_var = name_to_variable_.at(var); + if (std::find(input_variables_.begin(), input_variables_.end(), bdd_var) != input_variables_.end()) + return true; + return false; +} + +bool VarMgr::is_agent_variable(const std::string& var, std::size_t agent_id) const { + if (agent_id >= agent_variables_.size()) return false; + CUDD::BDD bdd_var = name_to_variable_.at(var); + if (std::find(agent_variables_[agent_id].begin(), agent_variables_[agent_id].end(), bdd_var) != agent_variables_[agent_id].end()) + return true; + return false; } CUDD::BDD VarMgr::state_variables_cube(std::size_t automaton_id) const { @@ -240,18 +505,20 @@ std::vector VarMgr::variable_labels() const { for (std::size_t id = 0; id < state_variables_.size(); ++id) { for (std::size_t i = 0; i < state_variables_[id].size(); ++i) { std::size_t index = state_variables_[id][i].NodeReadIndex(); - labels[index] = "A" + std::to_string(id) + ":Z" + std::to_string(i); + // labels[index] = "A" + std::to_string(id) + ":Z" + std::to_string(i); + labels[index] = index_to_name_.at(index); } } return labels; } -std::vector VarMgr::output_variable_labels() const { - std::vector labels(output_variable_count()); +std::vector VarMgr::agent_variable_labels(std::size_t agent_id) const { + if (agent_id >= agent_variables_.size()) return {}; + std::vector labels(agent_variables_[agent_id].size()); - for (std::size_t i = 0; i < output_variable_count(); ++i) { - std::size_t index = output_variables_[i].NodeReadIndex(); + for (std::size_t i = 0; i < agent_variables_[agent_id].size(); ++i) { + std::size_t index = agent_variables_[agent_id][i].NodeReadIndex(); labels[i] = index_to_name(index); } @@ -277,7 +544,8 @@ std::vector VarMgr::state_variable_labels( std::string id_string = "A" + std::to_string(automaton_id); for (std::size_t i = 0; i < state_variables_[automaton_id].size(); ++i) { - labels.push_back(id_string + ":Z" + std::to_string(i)); + // labels.push_back(id_string + ":Z" + std::to_string(i)); + labels.push_back(index_to_name_.at(state_variables_[automaton_id][i].NodeReadIndex())); } return labels; diff --git a/src/synthesis/source/automata/ExplicitStateDfa.cpp b/src/synthesis/source/automata/ExplicitStateDfa.cpp index 32954df4..8d7af06d 100644 --- a/src/synthesis/source/automata/ExplicitStateDfa.cpp +++ b/src/synthesis/source/automata/ExplicitStateDfa.cpp @@ -16,6 +16,16 @@ #include "lydia/to_dfa/strategies/compositional/base.hpp" #include "lydia/utils/print.hpp" + + +// #include + +#include +#include +#include + + + #include "cudd.h" namespace Syft { @@ -36,7 +46,6 @@ namespace Syft { auto t_start = std::chrono::high_resolution_clock::now(); -// logger.info("Transforming to DFA..."); auto t_dfa_start = std::chrono::high_resolution_clock::now(); auto ldlf_formula = whitemech::lydia::to_ldlf(formula); @@ -51,6 +60,25 @@ namespace Syft { return exp_dfa; } + ExplicitStateDfa ExplicitStateDfa::dfa_of_ldlf_formula(const whitemech::lydia::LDLfFormula& ldlf_formula) { + + auto dfa_strategy = whitemech::lydia::CompositionalStrategy(); + auto translator = whitemech::lydia::Translator(dfa_strategy); + + auto t_start = std::chrono::high_resolution_clock::now(); + + auto t_dfa_start = std::chrono::high_resolution_clock::now(); + + auto my_dfa = translator.to_dfa(ldlf_formula); + + auto my_mona_dfa = + std::dynamic_pointer_cast(my_dfa); + + DFA *d = dfaCopy(my_mona_dfa->dfa_); + + ExplicitStateDfa exp_dfa(d, my_mona_dfa->names); + return exp_dfa; + } ExplicitStateDfa ExplicitStateDfa::restrict_dfa_with_states(ExplicitStateDfa &d, std::vector restricted_states) { @@ -444,5 +472,598 @@ namespace Syft { return res_dfa; } + //set of functions taken from LydiaSyft Plus tool, to be used for obligation fragment synthesis + + std::vector ExplicitStateDfa::get_final() { + std::vector final_states; + DFA *dfa = get_dfa(); + for (int i = 0; i < dfa->ns; i++){ + if (dfa->f[i] == 1) { + final_states.push_back(i); + } + } + return final_states; + } + + std::size_t ExplicitStateDfa::get_initial() { + DFA *dfa = get_dfa(); + return dfa->s; + } + + ExplicitStateDfa + ExplicitStateDfa::dfa_to_Gdfa_obligation(const ExplicitStateDfa &input) { + ExplicitStateDfa d(input); + int d_ns = d.get_nb_states(); + int new_ns = d_ns + 1; // add a fresh initial state + int n = d.get_nb_variables(); + int new_len = d.names.size(); + + std::vector finals = d.get_final(); + std::vector is_final(d_ns, false); + for (auto s : finals) { + if (s < is_final.size()) { + is_final[s] = true; + } + } + + DFA *a = dfaMinimize(d.dfa_); + + int indices[new_len]; + for (int i = 0; i < d.indices.size(); ++i) { + indices[i] = d.indices[i]; + } + + dfaSetup(new_ns, new_len, indices); + + std::string statuses; + statuses.reserve(new_ns + 1); + + auto collect_transitions = [&](int state_idx, int offset) { + std::vector> transitions; + paths local_paths = make_paths(a->bddm, a->q[state_idx]); + paths iter = local_paths; + while (iter) { + auto guard = whitemech::lydia::get_path_guard(n, iter->trace); + transitions.emplace_back(iter->to + offset, guard); + iter = iter->next; + } + kill_paths(local_paths); + return transitions; + }; + + auto emit_state = [&](const std::vector>& transitions, + int default_target) { + dfaAllocExceptions(static_cast(transitions.size())); + for (const auto &p : transitions) { + std::vector guard(p.second.begin(), p.second.end()); + guard.push_back('\0'); + dfaStoreException(p.first, guard.data()); + } + dfaStoreState(default_target); + }; + + // New non-accepting initial state (index 0) copies behaviour of original initial + statuses += '-'; + auto initial_transitions = collect_transitions(0, 1); + int initial_default = initial_transitions.empty() ? 0 : initial_transitions.front().first; + emit_state(initial_transitions, initial_default); + + // Remaining states correspond to original ones, shifted by +1 + for (int i = 0; i < d_ns; ++i) { + int new_idx = i + 1; + if (is_final[i]) { + statuses += '+'; + auto transitions = collect_transitions(i, 1); + int default_target = transitions.empty() ? new_idx : transitions.front().first; + emit_state(transitions, default_target); + } else { + statuses += '-'; + dfaAllocExceptions(0); + dfaStoreState(new_idx); + } + } + + statuses.push_back('\0'); + DFA *tmp = dfaBuild(statuses.data()); + ExplicitStateDfa res(tmp, d.names); + return res; + } + + ExplicitStateDfa + ExplicitStateDfa::dfa_to_Fdfa_obligation(const ExplicitStateDfa &input) { + ExplicitStateDfa d(input); + int d_ns = d.get_nb_states(); + int new_ns = d_ns + 1; // add a fresh initial state + int n = d.get_nb_variables(); + int new_len = d.names.size(); + + std::vector final_states = d.get_final(); + std::vector is_final(d_ns, false); + for (auto s : final_states) { + if (s < is_final.size()) { + is_final[s] = true; + } + } + + DFA *a = dfaMinimize(d.dfa_); + + int indices[new_len]; + for (int i = 0; i < d.indices.size(); ++i) { + indices[i] = d.indices[i]; + } + + dfaSetup(new_ns, new_len, indices); + + auto collect_transitions = [&](int state_idx, int offset) { + std::vector> transitions; + paths local_paths = make_paths(a->bddm, a->q[state_idx]); + paths iter = local_paths; + while (iter) { + auto guard = whitemech::lydia::get_path_guard(n, iter->trace); + transitions.emplace_back(iter->to + offset, guard); + iter = iter->next; + } + kill_paths(local_paths); + return transitions; + }; + + auto emit_state = [&](const std::vector>& transitions, + int default_target) { + dfaAllocExceptions(static_cast(transitions.size())); + for (const auto &p : transitions) { + std::vector guard(p.second.begin(), p.second.end()); + guard.push_back('\0'); + dfaStoreException(p.first, guard.data()); + } + dfaStoreState(default_target); + }; + + std::string statuses; + statuses.reserve(new_ns + 1); + + // New initial state: always rejecting, mimics original initial moves + statuses += '-'; + auto initial_transitions = collect_transitions(0, 1); + int initial_default = initial_transitions.empty() ? 0 : initial_transitions.front().first; + emit_state(initial_transitions, initial_default); + + for (int i = 0; i < d_ns; ++i) { + int new_idx = i + 1; + if (is_final[i]) { + statuses += '+'; + dfaAllocExceptions(0); + dfaStoreState(new_idx); + } else { + statuses += '-'; + auto transitions = collect_transitions(i, 1); + int default_target = transitions.empty() ? new_idx : transitions.front().first; + emit_state(transitions, default_target); + } + } + + statuses.push_back('\0'); + DFA *tmp = dfaBuild(statuses.data()); + ExplicitStateDfa res(tmp, d.names); + return res; + } + + + ExplicitStateDfa + ExplicitStateDfa::dfa_to_Gdfa(ExplicitStateDfa &d) { + // std::cout << "--------- d:\n"; + // d.dfa_print(); + int d_ns = d.get_nb_states(); + int new_ns = d.get_final().size() + 2; // initial state is "0" and sink state is "new_ns" + int n = d.get_nb_variables(); + int new_len = d.names.size(); + + bool safe_states[d_ns]; + int state_map[d_ns]; + memset(safe_states, false, sizeof(safe_states)); + memset(state_map, -1, sizeof(state_map)); + + safe_states[0] = true; // we would like to keep initial state + for (auto s: d.get_final()) { + safe_states[s] = true; + } + + int index = 0; + for (int i = 0; i < d_ns; i++) { + if (!safe_states[i]) continue; + state_map[i] = index++; + } // relabel all safe states + + DFA *a = d.dfa_; + DFA *result; + paths state_paths, pp; + std::string statuses; + + int indices[new_len]; + for (int i = 0; i < d.indices.size(); i++) { + indices[i] = d.indices[i]; + } + + dfaSetup(new_ns, new_len, indices); + + for (int i = 0; i < a->ns; i++) { + // ignore non-safe_states + if (!safe_states[i]) continue; + int next_state; + std::string next_guard; + + auto transitions = std::vector>(); + state_paths = pp = make_paths(a->bddm, a->q[i]); + while (pp) { + auto guard = whitemech::lydia::get_path_guard(n, pp->trace); + // ignore non safe_states + if (safe_states[pp->to]) { + transitions.emplace_back(pp->to, guard); + } + pp = pp->next; + } + if (i == 0) { + statuses += "-"; + } else { + statuses += "+"; + } +// statuses += "+"; + // transitions + int nb_transitions = transitions.size(); + dfaAllocExceptions(nb_transitions); + for (const auto &p: transitions) { + std::tie(next_state, next_guard) = p; + dfaStoreException(state_map[next_state], next_guard.data()); + } + dfaStoreState(new_ns-1); + kill_paths(state_paths); + } + + statuses += "-"; + dfaAllocExceptions(0); + dfaStoreState(new_ns-1); + + DFA *tmp = dfaBuild(statuses.data()); + ExplicitStateDfa res1(tmp, d.names); + + // res1.dfa_print(); + //result = dfaMinimize(tmp); + ExplicitStateDfa res(tmp, d.names); + // std::cout << "--------- Gd:\n"; + // res.dfa_print(); + return res; + } + + ExplicitStateDfa + ExplicitStateDfa::dfa_to_Fdfa(ExplicitStateDfa &d) { + int d_ns = d.get_nb_states(); + std::vector final_states = d.get_final(); + int n = d.get_nb_variables(); + int new_len = d.names.size(); + +// int state_map[d_ns]; +// memset(state_map, -1, sizeof(state_map)); +// +// int index = 0; +// for (int i = 0; i < d_ns; i++) { +// state_map[i] = index++; +// } + + DFA *a = d.dfa_; + DFA *result; + paths state_paths, pp; + std::string statuses; + + int indices[new_len]; + for (int i = 0; i < d.indices.size(); i++) { + indices[i] = d.indices[i]; + } + + dfaSetup(d_ns, new_len, indices); + + for (int i = 0; i < a->ns; i++) { + + int next_state; + std::string next_guard; + + auto transitions = std::vector>(); + state_paths = pp = make_paths(a->bddm, a->q[i]); + auto it = find(final_states.begin(), final_states.end(), i); + while (pp) { + auto guard = whitemech::lydia::get_path_guard(n, pp->trace); + if (it != final_states.end()) { + transitions.emplace_back(i, guard); + } else { + transitions.emplace_back(pp->to, guard); + } + + pp = pp->next; + } + if (it != final_states.end()) { + statuses += "+"; + } else { + statuses += "-"; + } + + // transitions + int nb_transitions = transitions.size(); + dfaAllocExceptions(nb_transitions); + for (const auto &p: transitions) { + std::tie(next_state, next_guard) = p; + dfaStoreException(next_state, next_guard.data()); + } + dfaStoreState(d_ns); + kill_paths(state_paths); + } + +// statuses += "+"; +// dfaAllocExceptions(0); +// dfaStoreState(d_ns); + + DFA *tmp = dfaBuild(statuses.data()); + //result = dfaMinimize(tmp); + ExplicitStateDfa res(tmp, d.names); + return res; + } + + + ExplicitStateDfa + ExplicitStateDfa::dfa_remove_initial_self_loops(ExplicitStateDfa &d) { + // std::cout << "--------- remove initial loops:\n"; + // d.dfa_print(); + int d_ns = d.get_nb_states(); + int new_ns = d_ns + 1; // initial state is "0", and new state is new_ns-1 + int n = d.get_nb_variables(); + int new_len = d.names.size(); + + std::vector final_states = d.get_final(); + + DFA *a = d.dfa_; + DFA *result; + paths state_paths, pp; + std::string statuses; + + int indices[new_len]; + for (int i = 0; i < d.indices.size(); i++) { + indices[i] = d.indices[i]; + } + + dfaSetup(new_ns, new_len, indices); + + int next_state; + std::string next_guard; + + auto transitions = std::vector>(); + state_paths = pp = make_paths(a->bddm, a->q[0]); + // auto it = find(final_states.begin(), final_states.end(), i); + + while (pp) { + auto guard = whitemech::lydia::get_path_guard(n, pp->trace); + transitions.emplace_back(pp->to+1, guard); + pp = pp->next; + } + statuses += "+"; + + // transitions + int nb_transitions = transitions.size(); + dfaAllocExceptions(nb_transitions); + for (const auto &p: transitions) { + std::tie(next_state, next_guard) = p; + dfaStoreException(next_state, next_guard.data()); + } + dfaStoreState(new_ns); + kill_paths(state_paths); + + for (int i = 0; i < a->ns; i++) { + int next_state; + std::string next_guard; + + auto transitions = std::vector>(); + state_paths = pp = make_paths(a->bddm, a->q[i]); + auto it = find(final_states.begin(), final_states.end(), i); + + while (pp) { + auto guard = whitemech::lydia::get_path_guard(n, pp->trace); + transitions.emplace_back(pp->to+1, guard); + + pp = pp->next; + } + + + + + if (it != final_states.end()) { + statuses += "+"; + } else { + statuses += "-"; + } + + // transitions + int nb_transitions = transitions.size(); + dfaAllocExceptions(nb_transitions); + for (const auto &p: transitions) { + std::tie(next_state, next_guard) = p; + dfaStoreException(next_state, next_guard.data()); + } + dfaStoreState(d_ns); + kill_paths(state_paths); + } + +// statuses += "+"; +// dfaAllocExceptions(0); +// dfaStoreState(d_ns); + + DFA *tmp = dfaBuild(statuses.data()); + // result = dfaMinimize(tmp); + ExplicitStateDfa res(tmp, d.names); + return res; + } + + ExplicitStateDfa ExplicitStateDfa::dfa_minimize_weak(const ExplicitStateDfa &d) { + DFA* a = d.dfa_; + int ns = a->ns; + + // Build a Boost graph from the DFA + typedef boost::adjacency_list Graph; + Graph g(ns); + // Store if a vertex has a self-loop + std::vector has_self_loop(ns, false); + + // Add edges to the graph + for (int v = 0; v < ns; v++) { + paths state_paths = make_paths(a->bddm, a->q[v]); + paths tp = state_paths; + std::set successors; + while (tp) { + successors.insert(tp->to); + tp = tp->next; + } + kill_paths(state_paths); + + for (int succ : successors) { + boost::add_edge(v, succ, g); + if (succ == v) { + has_self_loop[v] = true; + } + } + } + + // Step 1: Compute SCCs using Boost's strong_components + std::vector scc_id(ns); + int num_sccs = boost::strong_components(g, &scc_id[0]); + + // Analyze SCCs - check if recurrent and accepting + std::vector is_recurrent(num_sccs, false); + std::vector scc_is_accepting(num_sccs, false); + std::vector scc_size(num_sccs, 0); + + // Count SCC sizes and check if all states in SCC are final + std::vector all_final_in_scc(num_sccs, true); + for (int i = 0; i < ns; i++) { + scc_size[scc_id[i]]++; + if (a->f[i] != 1) { + all_final_in_scc[scc_id[i]] = false; + } + } + + // Determine which SCCs are recurrent + for (int scc = 0; scc < num_sccs; scc++) { + if (scc_size[scc] > 1) { + // Multi-state SCC is always recurrent + is_recurrent[scc] = true; + } else { + // Single-state SCC is recurrent only if it has a self-loop + for (int v = 0; v < ns; v++) { + if (scc_id[v] == scc) { + if (has_self_loop[v]) { + is_recurrent[scc] = true; + break; + } + } + } + } + if (is_recurrent[scc]) { + scc_is_accepting[scc] = all_final_in_scc[scc]; + } + } + + // Step 2: Build SCC graph and compute topological order + typedef boost::adjacency_list SCCGraph; + SCCGraph scc_graph(num_sccs); + + // Build SCC graph by collapsing edges of g into edges between SCC ids. + typedef boost::graph_traits::edge_iterator edge_iter; + edge_iter ei, ei_end; + for (boost::tie(ei, ei_end) = boost::edges(g); ei != ei_end; ++ei) { + int u = boost::source(*ei, g); + int v = boost::target(*ei, g); + int su = scc_id[u]; + int sv = scc_id[v]; + if (su != sv) { + boost::add_edge(su, sv, scc_graph); + } + } + + // Compute topological sort + std::vector topo_order; + topo_order.reserve(num_sccs); + try { + boost::topological_sort(scc_graph, std::back_inserter(topo_order)); + } catch (boost::not_a_dag&) { + // This shouldn't happen with the SCC graph, but handle it gracefully + std::cerr << "Warning: SCC graph is not a DAG, using original automaton" << std::endl; + ExplicitStateDfa result(dfaCopy(a), d.names); + return result; + } + + // Step 3: Compute maximal coloring following Löding's algorithm (Fig. 1) + const int k = (num_sccs | 1) + 1; // Large enough even number + std::vector scc_color(num_sccs); + + for (int vi : topo_order) { + // Get successor SCCs + std::set succ_sccs; + auto edge_range = boost::out_edges(vi, scc_graph); + for (auto it = edge_range.first; it != edge_range.second; ++it) { + succ_sccs.insert(boost::target(*it, scc_graph)); + } + + if (succ_sccs.empty()) { + // No successors - terminal SCC + if (scc_is_accepting[vi]) { + scc_color[vi] = k; // even (accepting) + } else { + scc_color[vi] = k + 1; // odd (rejecting) + } + } else { + // Has successors - compute minimum successor color + int min_succ_color = k + 1; + for (int succ : succ_sccs) { + min_succ_color = std::min(min_succ_color, scc_color[succ]); + } + + if (is_recurrent[vi]) { + // Recurrent SCC - assign based on acceptance + bool is_even = (min_succ_color % 2 == 0); + if (is_even && scc_is_accepting[vi]) { + scc_color[vi] = min_succ_color; + } else if (!is_even && !scc_is_accepting[vi]) { + scc_color[vi] = min_succ_color; + } else { + scc_color[vi] = min_succ_color - 1; + } + } else { + // Transient SCC - inherit color from successors + scc_color[vi] = min_succ_color; + } + } + } + // Print out the colouring in SCC order + spdlog::trace("SCC Coloring Results:"); + for (int scc = 0; scc < num_sccs; scc++) { + spdlog::trace("SCC {}: Color {}, Recurrent: {}, Accepting: {}", + scc, scc_color[scc], is_recurrent[scc], scc_is_accepting[scc]); + } + // Step 4: Set final states based on coloring (even = final, odd = non-final) + DFA* normalized = dfaCopy(a); + for (int i = 0; i < ns; i++) { + int color = scc_color[scc_id[i]]; + spdlog::trace("State {} in SCC {} colored {}", i, scc_id[i], color); + // Log if state was final before + spdlog::trace("State {} was final before: {}", i, normalized->f[i]); + normalized->f[i] = (color % 2 == 0) ? 1 : -1; + } + + // Step 5: Apply standard DFA minimization + DFA* minimized = dfaMinimize(normalized); + dfaFree(normalized); + // Log number of states now and before + // spdlog::info("[ExplicitStateDfa::minise] Number of states before minimization: {}", ns); + // spdlog::info("[ExplicitStateDfa::minise] Number of states after minimization: {}", minimized->ns); + ExplicitStateDfa result(minimized, d.names); + return result; + } + + + } diff --git a/src/synthesis/source/automata/SymbolicStateDfa.cpp b/src/synthesis/source/automata/SymbolicStateDfa.cpp index bf270906..5fbe95d8 100644 --- a/src/synthesis/source/automata/SymbolicStateDfa.cpp +++ b/src/synthesis/source/automata/SymbolicStateDfa.cpp @@ -1,10 +1,23 @@ #include "automata/SymbolicStateDfa.h" +#include "game/BuchiReachability_multiagent.hpp" +#include namespace Syft { SymbolicStateDfa::SymbolicStateDfa(std::shared_ptr var_mgr) : var_mgr_(std::move(var_mgr)) {} + SymbolicStateDfa::SymbolicStateDfa(std::shared_ptr var_mgr, + std::size_t automaton_id, + const std::vector& initial_state, + const std::vector& transition_function, + const CUDD::BDD& final_states) : + var_mgr_(std::move(var_mgr)), + automaton_id_(automaton_id), + initial_state_(initial_state), + transition_function_(transition_function), + final_states_(final_states) {} + std::pair SymbolicStateDfa::create_state_variables( std::shared_ptr &var_mgr, std::size_t state_count) { @@ -299,5 +312,99 @@ namespace Syft { return complement_automaton; } + + +SymbolicStateDfa SymbolicStateDfa::get_CORE(const Syft::SymbolicStateDfa &dfa, Actor protagonist_actor, Actor starting_actor) { + + auto var_mgr = dfa.var_mgr(); + //STEP 1: Get winning region for the agent using BuchiReachability + size_t num_agents = var_mgr->agents_count(); + CUDD::BDD buchi_condition = dfa.final_states(); + CUDD::BDD state_space = var_mgr->cudd_mgr()->bddOne(); + + BuchiReachability_multiagent game(dfa, starting_actor, protagonist_actor, buchi_condition, state_space, num_agents); + SynthesisResult result = game.run(); + std::cout << (protagonist_actor.is_agent() ? "Agent core is realizable: " : "Environment core is realizable: ") + << result.realizability << std::endl; + CUDD::BDD winning_region = result.winning_states; + + auto initial_state = dfa.initial_state(); + + //chech if initial state is in winning region + //std::cout << "initial state of automata non restricted is in winning region? " << (winning_region.Eval(initial_state.data()).IsOne() ? 1 : 0) << std::endl; + + //STEP 2: create a new dfa with the same values of the original Dfa + STEP 3) create new sink + std::string sink_name = "z_sink_" + (protagonist_actor.is_environment() ? "env" : "agent" + std::to_string(protagonist_actor.id())); + std::size_t new_id = var_mgr->create_core_state_space(dfa.automaton_id(), sink_name); + + + std::string unique_sink_name = sink_name + "_" + std::to_string(new_id); + CUDD::BDD z_sink = var_mgr->name_to_variable(unique_sink_name); + + //STEP 4:set BDD for z_sinz when actor is an agent + //STEP 5:set BDD for z_sink when actor is environment + std::vector compose_vector = var_mgr->make_compose_vector( + dfa.automaton_id(), + dfa.transition_function_ + ); + + CUDD::BDD next_W = winning_region.VectorCompose(compose_vector); + + CUDD::BDD z_sink_bdd; + if(protagonist_actor.is_agent()){ + CUDD::BDD external_variables_cube = var_mgr->agent_input_cube(protagonist_actor.id()); + CUDD::BDD forallX_nextW = next_W.UnivAbstract(external_variables_cube); + CUDD::BDD t_Z_Y = winning_region * forallX_nextW; + z_sink_bdd = z_sink + (winning_region * !t_Z_Y) + (!winning_region); + } else { + CUDD::BDD t_Z_Y_X = winning_region * next_W; + z_sink_bdd = z_sink + (winning_region * !t_Z_Y_X) + (!winning_region); + } + + std::vector core_initial_state = dfa.initial_state(); + core_initial_state.push_back(0); + + std::vector core_transition_function = dfa.transition_function(); + core_transition_function.push_back(z_sink_bdd); + + //STEP 6: Modify final states function + CUDD::BDD core_final_states = dfa.final_states() * winning_region * !z_sink; + + SymbolicStateDfa core_dfa(std::move(var_mgr)); + core_dfa.automaton_id_ = new_id; + core_dfa.initial_state_ = std::move(core_initial_state); + core_dfa.transition_function_ = std::move(core_transition_function); + core_dfa.final_states_ = std::move(core_final_states); + + return core_dfa; +} + +SymbolicStateDfa SymbolicStateDfa::get_NE(const Syft::SymbolicStateDfa &dfa, Actor protagonist_actor) { + auto var_mgr = dfa.var_mgr(); + std::string ne_name = "z_ne_" + (protagonist_actor.is_environment() ? "env" : "agent" + std::to_string(protagonist_actor.id())); + std::size_t new_id = var_mgr->create_nonempty_state_space(dfa.automaton_id(), ne_name); + + std::string unique_ne_name = ne_name + "_" + std::to_string(new_id); + CUDD::BDD ne_sink = var_mgr->name_to_variable(unique_ne_name); + + std::vector ne_initial_state = dfa.initial_state(); + ne_initial_state.push_back(0); + + std::vector ne_transition_function = dfa.transition_function(); + ne_transition_function.push_back(var_mgr->cudd_mgr()->bddOne()); + + CUDD::BDD ne_final_states = dfa.final_states() * ne_sink; + + + SymbolicStateDfa ne_dfa(std::move(var_mgr)); + ne_dfa.automaton_id_ = new_id; + ne_dfa.initial_state_ = std::move(ne_initial_state); + ne_dfa.transition_function_ = std::move(ne_transition_function); + ne_dfa.final_states_ = std::move(ne_final_states); + + return ne_dfa; + + +} } diff --git a/src/synthesis/source/game/BuchiReachability_multiagent.cpp b/src/synthesis/source/game/BuchiReachability_multiagent.cpp new file mode 100644 index 00000000..bb31e646 --- /dev/null +++ b/src/synthesis/source/game/BuchiReachability_multiagent.cpp @@ -0,0 +1,108 @@ +// +// Created by shuzhu on 16/04/24. +// + +#include "game/BuchiReachability_multiagent.hpp" +#include "game/DfaGameSynthesizer_multiagent.h" + +namespace Syft { + BuchiReachability_multiagent::BuchiReachability_multiagent(const SymbolicStateDfa &spec, Actor starting_actor, + Actor protagonist_actor, + const CUDD::BDD &Buchi, + const CUDD::BDD &state_space, + size_t num_agents) + : DfaGameSynthesizer_multiagent(spec, starting_actor, protagonist_actor, num_agents), + Buchi_(Buchi), + state_space_(state_space) { + } + + SynthesisResult BuchiReachability_multiagent::run() const { + SynthesisResult result; + CUDD::BDD winning_states = state_space_; + CUDD::BDD winning_moves = winning_states; + int c = 0; + + while (true) { + + var_mgr_->dump_dot(winning_states.Add(), "winning_states_"+ std::to_string(c)+".dot"); + var_mgr_->dump_dot(winning_moves.Add(), "winning_moves_"+ std::to_string(c)+".dot"); + + + CUDD::BDD new_winning_states, new_winning_moves; + // Outer greatest fixpoint: W_{i+1} + CUDD::BDD pre_adv_W = preimage(winning_states); + // Inner least fixpoint initialization: Z_{0} + CUDD::BDD projection = project_into_states(pre_adv_W); + CUDD::BDD inner_winning_states = Buchi_ & projection; + //CUDD::BDD inner_winning_moves = inner_winning_states; + CUDD::BDD inner_winning_moves = preimage(inner_winning_states); + //int inner_c = 0; + + while (true) { + // var_mgr_->dump_dot(inner_winning_states.Add(), "inner_winning_states_"+ std::to_string(c)+".dot"); + // var_mgr_->dump_dot(inner_winning_moves.Add(), "inner_winning_moves_"+ std::to_string(c)+".dot"); + + + CUDD::BDD new_inner_winning_states, new_inner_winning_moves; + // PreAdv(Z_j) - compute preimage of inner_winning_states + CUDD::BDD pre_adv_Z = preimage(inner_winning_states); + if(starting_actor_.is_agent()){ + new_inner_winning_moves = inner_winning_moves | + (state_space_ & (!inner_winning_states) & pre_adv_Z); + new_inner_winning_states = project_into_states(new_inner_winning_moves); + }else{ + // Inner least fixpoint: Z_{j+1} + CUDD::BDD new_collected_winning_states = project_into_states(pre_adv_Z); + new_inner_winning_states = inner_winning_states | new_collected_winning_states; + new_inner_winning_moves = inner_winning_moves | + ((!inner_winning_states) & new_collected_winning_states + & pre_adv_Z); + } + + // Check for fixpoint of inner least fixpoint + if (new_inner_winning_states == inner_winning_states) { + if(starting_actor_.is_agent()){ + new_winning_moves = winning_moves & new_inner_winning_moves; + new_winning_states = winning_states & inner_winning_states; + }else{ + CUDD::BDD transition_to_winning_states = preimage(inner_winning_states); + new_winning_states = winning_states & inner_winning_states; + new_winning_moves = winning_moves & transition_to_winning_states; + } + break; + } + + inner_winning_moves = new_inner_winning_moves; + inner_winning_states = new_inner_winning_states; + //inner_c++; + } + + if(new_winning_states == winning_states){ + + + if (includes_initial_state(new_winning_states)) { + result.realizability = true; + result.winning_states = new_winning_states; + result.winning_moves = new_winning_moves; + result.transducer_multiagent = AbstractSingleStrategy(result); + result.transducer = nullptr; + return result; + + } else { + result.realizability = false; + result.winning_states = new_winning_states; + result.winning_moves = new_winning_moves; + result.transducer_multiagent = nullptr; + result.transducer = nullptr; + return result; + } + } + + winning_moves = new_winning_moves; + winning_states = new_winning_states; + c++; + } + } + +} + diff --git a/src/synthesis/source/game/DfaGameSynthesizer.cpp b/src/synthesis/source/game/DfaGameSynthesizer.cpp index a60685cc..9f3a080d 100644 --- a/src/synthesis/source/game/DfaGameSynthesizer.cpp +++ b/src/synthesis/source/game/DfaGameSynthesizer.cpp @@ -59,7 +59,11 @@ namespace Syft { CUDD::BDD DfaGameSynthesizer::project_into_states( const CUDD::BDD &winning_moves) const { - return quantify_non_state_variables_->apply(winning_moves); + + CUDD::BDD result = quantify_non_state_variables_->apply(winning_moves); + std::cout << "DFA game synthesizer_project into states: " << result << std::endl; + + return result; } bool DfaGameSynthesizer::includes_initial_state( diff --git a/src/synthesis/source/game/DfaGameSynthesizer_multiagent.cpp b/src/synthesis/source/game/DfaGameSynthesizer_multiagent.cpp new file mode 100644 index 00000000..1bce1feb --- /dev/null +++ b/src/synthesis/source/game/DfaGameSynthesizer_multiagent.cpp @@ -0,0 +1,170 @@ +#include "game/DfaGameSynthesizer_multiagent.h" +#include "Actor.h" +#include "game/Quantification.h" +#include + +namespace Syft { + + DfaGameSynthesizer_multiagent::DfaGameSynthesizer_multiagent(SymbolicStateDfa spec, + Actor starting_actor, + Actor protagonist_actor, + size_t num_agents) + : Synthesizer(spec), starting_actor_(starting_actor), + protagonist_actor_(protagonist_actor), num_total_agents_(num_agents) { + var_mgr_ = spec_.var_mgr(); + + // Make versions of the initial state and transition function that can be used + // with CUDD::BDD::Eval and CUDD::BDD::VectorCompose, respectively + initial_vector_ = var_mgr_->make_eval_vector(spec_.automaton_id(), + spec_.initial_state()); + transition_vector_ = var_mgr_->make_compose_vector( + spec_.automaton_id(), spec_.transition_function()); + + + CUDD::BDD input_cube = var_mgr_->cudd_mgr()->bddOne(); + CUDD::BDD output_cube = var_mgr_->cudd_mgr()->bddOne(); + + if(protagonist_actor_.is_environment()){ + //the protagonist is env + input_cube = var_mgr_->environment_input_cube(); + output_cube = var_mgr_->environment_output_cube(); + }else{ + //the protagonist is an agent + std::size_t id = protagonist_actor.id(); + input_cube = var_mgr_->agent_input_cube(id); + output_cube = var_mgr_->agent_output_cube(id); + } + + if (starting_actor_.is_environment()) { + if (protagonist_actor_.is_environment()) { + quantify_independent_variables_ = std::make_unique(input_cube); + quantify_non_state_variables_ = std::make_unique(output_cube); + } else { + quantify_independent_variables_ = std::make_unique(); + quantify_non_state_variables_ = std::make_unique(input_cube, + output_cube); + } + } else { + if (protagonist_actor_.is_environment()) { + quantify_independent_variables_ = std::make_unique(); + quantify_non_state_variables_ = std::make_unique(input_cube, + output_cube); + } else { + quantify_independent_variables_ = std::make_unique(input_cube); + quantify_non_state_variables_ = std::make_unique(output_cube); + } + } + } +//-------------------------------OLD + CUDD::BDD DfaGameSynthesizer_multiagent::preimage( + const CUDD::BDD &winning_states) const { + // Transitions that move into a winning state + CUDD::BDD winning_transitions = + winning_states.VectorCompose(transition_vector_); + + // Quantify all variables that the outputs don't depend on + return quantify_independent_variables_->apply(winning_transitions); + } + + CUDD::BDD DfaGameSynthesizer_multiagent::project_into_states( + const CUDD::BDD &winning_moves) const { + return quantify_non_state_variables_->apply(winning_moves); + } + + bool DfaGameSynthesizer_multiagent::includes_initial_state( + const CUDD::BDD &winning_states) const { + // Need to create a copy if we want to define the function as const, since + // CUDD::BDD::Eval does not take the data as const + std::vector copy(initial_vector_); + + return winning_states.Eval(copy.data()).IsOne(); + } + + std::unordered_map DfaGameSynthesizer_multiagent::synthesize_strategy(const CUDD::BDD &winning_moves, + const std::shared_ptr &var_mgr) const { + std::vector parameterized_output_function; + int *output_indices = nullptr; + + //Identify the target (Adam) + CUDD::BDD target_cube; + std::size_t target_count; + + if(protagonist_actor_.is_agent()){ + target_cube = var_mgr->agent_output_cube(protagonist_actor_.id()); + target_count = var_mgr->agent_variable_count(protagonist_actor_.id()); + } else { + target_cube = var_mgr->environment_output_cube(); + target_count = var_mgr->environment_variable_count(); + } + + if(target_count == 0) return {}; + + // Need to negate the BDD because b.SolveEqn(...) solves the equation b = 0 + CUDD::BDD pre = (!winning_moves).SolveEqn(target_cube, + parameterized_output_function, + &output_indices, + (int)target_count); + + // Copy the index since it will be necessary in the last step + std::vector index_copy(target_count); + + for (std::size_t i = 0; i < target_count; ++i) { + index_copy[i] = output_indices[i]; + } + + // Verify that the solution is correct, also frees output_index + CUDD::BDD verified = (!winning_moves).VerifySol(parameterized_output_function, + output_indices); + + assert(pre == verified); + + //Build strategic map + std::unordered_map output_function; + + // Let y_i be the i-th output variable in the BDD ordering. The parameterized + // output function for y_i is of the form f_i(x_1, ..., x_m, p_i, ..., p_n) + // where p_i, ..., p_n are parameters taking the place of y_i, ..., y_n. All + // f_i are such that no matter what we replace p_i, ..., p_n with, the result + // is a valid output function. We replace the parameters with 1 so that all + // f_i are dependent only on the input and state variables. + for (int i = (int)target_count - 1; i >= 0; --i) { + int output_index = index_copy[i]; + + output_function[output_index] = parameterized_output_function[i]; + + for (int j = (int)target_count - 1; j >= i; --j) { + int parameter_index = index_copy[j]; + + // Can be anything, set to the constant 1 for simplicity + CUDD::BDD parameter_value = var_mgr->cudd_mgr()->bddOne(); + + output_function[output_index] = + output_function[output_index].Compose(parameter_value, + parameter_index); + } + } + + //if(output_indices != nullptr) free(output_indices); + + return output_function; + } + + std::unique_ptr DfaGameSynthesizer_multiagent::AbstractSingleStrategy(const SynthesisResult &result) const { + return abstract_single_strategy(result.winning_moves, var_mgr_, initial_vector_, spec_.transition_function(), + starting_actor_, protagonist_actor_); + } + + std::unique_ptr DfaGameSynthesizer_multiagent::abstract_single_strategy( + const CUDD::BDD &winning_moves, + const std::shared_ptr& var_mgr, + const std::vector& initial_vector, + const std::vector& transition_vector, + Actor starting_actor, + Actor protagonist_actor) const { + std::unordered_map strategy = synthesize_strategy(winning_moves, var_mgr); + auto transducer_multiagent = std::make_unique(var_mgr, initial_vector, strategy, transition_vector, starting_actor, + protagonist_actor); + return transducer_multiagent; + } + +} diff --git a/src/synthesis/source/game/InputOutputPartition.cpp b/src/synthesis/source/game/InputOutputPartition.cpp index e68c1462..d82c599e 100644 --- a/src/synthesis/source/game/InputOutputPartition.cpp +++ b/src/synthesis/source/game/InputOutputPartition.cpp @@ -19,45 +19,63 @@ InputOutputPartition::InputOutputPartition() InputOutputPartition InputOutputPartition::read_from_file( const std::string& filename) { - InputOutputPartition partition; - + InputOutputPartition partition; std::ifstream in(filename); - std::size_t line_number = 1; + if (!in.is_open()) { + throw std::runtime_error("Impossibile aprire il file: " + filename); + } + + std::size_t line_number = 0; std::string line; - std::getline(in, line); - - std::vector input_substr; - input_substr = Syft::split(line, ":"); - if (input_substr.size() != 2 || input_substr[0] != ".inputs") { - throw bad_file_format_exception(line_number); - } + auto get_next_valid_line = [&in, &line, &line_number]() -> bool { + while (std::getline(in, line)) { + line_number++; + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + line = Syft::trim(line); + if (!line.empty()) return true; + } + return false; + }; - std::string trimmed_input_substr = Syft::trim(input_substr[1]); // remove leading and trailing whitespace - partition.input_variables = Syft::split(trimmed_input_substr, " "); + if (!get_next_valid_line()) throw bad_file_format_exception(line_number); - ++line_number; - std::getline(in, line); - - std::vector output_substr; - output_substr = Syft::split(line, ":"); + std::vector input_substr = Syft::split(line, ":"); - if (output_substr.size() != 2 || output_substr[0] != ".outputs") { + if (input_substr.size() != 2 || Syft::trim(input_substr[0]) != ".inputs") { throw bad_file_format_exception(line_number); } - std::string trimmed_output_substr = Syft::trim(output_substr[1]); // remove leading and trailing whitespace - partition.output_variables = Syft::split(trimmed_output_substr, " "); + std::string trimmed_input_vals = Syft::trim(input_substr[1]); + partition.input_variables = Syft::split(trimmed_input_vals, " "); + + // Read agents + std::size_t agent_id = 0; + while (get_next_valid_line()) { + std::vector agent_substr = Syft::split(line, ":"); + std::string expected_tag = ".agent" + std::to_string(agent_id); + + if (agent_substr.size() != 2 || Syft::trim(agent_substr[0]) != expected_tag) { + throw bad_file_format_exception(line_number); + } + + std::string trimmed_agent_vals = Syft::trim(agent_substr[1]); + partition.agent_variables.emplace_back(Syft::split(trimmed_agent_vals, " ")); + ++agent_id; + } return partition; } InputOutputPartition InputOutputPartition::construct_from_input(const std::vector inputs_substr, - const std::vector outputs_substr) { + const std::vector> agents_substr) { InputOutputPartition partition; partition.input_variables = inputs_substr; - partition.output_variables = outputs_substr; + partition.agent_variables = agents_substr; + if (!agents_substr.empty()) { + partition.output_variables = agents_substr[0]; // Backward compatibility + } return partition; } @@ -65,8 +83,9 @@ InputOutputPartition InputOutputPartition::construct_from_input(const std::vecto return std::find(input_variables.begin(), input_variables.end(), var_name) != input_variables.end(); } - bool InputOutputPartition::is_output(const std::string &var_name) { - return std::find(output_variables.begin(), output_variables.end(), var_name) != output_variables.end(); + bool InputOutputPartition::is_agent(const std::string &var_name, std::size_t agent_id) { + if (agent_id >= agent_variables.size()) return false; + return std::find(agent_variables[agent_id].begin(), agent_variables[agent_id].end(), var_name) != agent_variables[agent_id].end(); } } diff --git a/src/synthesis/source/game/Reachability_multiagent.cpp b/src/synthesis/source/game/Reachability_multiagent.cpp new file mode 100644 index 00000000..73caa995 --- /dev/null +++ b/src/synthesis/source/game/Reachability_multiagent.cpp @@ -0,0 +1,61 @@ +// +// Created by shuzhu on 16/04/24. +// + +#include "game/Reachability_multiagent.hpp" + +namespace Syft { + Reachability_multiagent::Reachability_multiagent(const SymbolicStateDfa &spec, Actor starting_actor, + Actor protagonist_actor, const CUDD::BDD &goal_states, + const CUDD::BDD &state_space, size_t num_agents) + : DfaGameSynthesizer_multiagent(spec, starting_actor, protagonist_actor, num_agents), + goal_states_(goal_states), + state_space_(state_space) { + } + + SynthesisResult Reachability_multiagent:: run() const { + SynthesisResult result; + CUDD::BDD winning_states = state_space_ & goal_states_; + CUDD::BDD winning_moves = winning_states; + + while (true) { + CUDD::BDD new_winning_states, new_winning_moves; + + if (starting_actor_.is_agent()){ + CUDD::BDD quantified_X_transitions_to_winning_states = preimage(winning_states); + new_winning_moves = winning_moves | + (state_space_ & (!winning_states) & quantified_X_transitions_to_winning_states); + + new_winning_states = project_into_states(new_winning_moves); + } else { + CUDD::BDD transitions_to_winning_states = preimage(winning_states); + CUDD::BDD new_collected_winning_states = project_into_states(transitions_to_winning_states); + new_winning_states = winning_states | new_collected_winning_states; + new_winning_moves = winning_moves | + ((!winning_states) & new_collected_winning_states & transitions_to_winning_states); + } + + if (includes_initial_state(new_winning_states)) { + result.realizability = true; + result.winning_states = new_winning_states; + result.winning_moves = new_winning_moves; + result.transducer_multiagent = AbstractSingleStrategy(result); + result.transducer = nullptr; + return result; + + } else if (new_winning_states == winning_states) { + result.realizability = false; + result.winning_states = new_winning_states; + result.winning_moves = new_winning_moves; + result.transducer_multiagent = nullptr; + result.transducer = nullptr; + return result; + } + + winning_moves = new_winning_moves; + winning_states = new_winning_states; + } + } + +} + diff --git a/src/synthesis/source/game/Transducer_multiagent.cpp b/src/synthesis/source/game/Transducer_multiagent.cpp new file mode 100644 index 00000000..a934946e --- /dev/null +++ b/src/synthesis/source/game/Transducer_multiagent.cpp @@ -0,0 +1,46 @@ +#include "game/Transducer_multiagent.h" + +#include + +namespace Syft { + +Transducer_multiagent::Transducer_multiagent(const std::shared_ptr& var_mgr, + const std::vector& initial_vector, + const std::unordered_map& output_function, + const std::vector& transition_function, + Actor starting_actor, + Actor protagonist_actor) + : var_mgr_(var_mgr), + initial_vector_(initial_vector), + output_function_(output_function), + transition_function_(transition_function), + starting_actor_(starting_actor), + protagonist_actor_(protagonist_actor) +{} + +void Transducer_multiagent::dump_dot(const std::string& filename) const { + //var_mgr_->print_mgr(); + std::vector actor_labels; + //std::cout << "Current actor: " << (protagonist_actor_.is_environment() ? "ENV" : "Agent " + std::to_string(protagonist_actor_.id())) << std::endl; + + if (protagonist_actor_.is_environment()) { + actor_labels = var_mgr_->input_variable_labels(); + } else { + actor_labels = var_mgr_->agent_variable_labels(protagonist_actor_.id()); + } + + std::size_t output_count = output_function_.size(); + std::vector output_vector(output_count); + + for (std::size_t i = 0; i < output_count; ++i) { + std::string label = actor_labels[i]; + //std::cout << "Label: " << label << std::endl; + int index = var_mgr_->name_to_variable(label).NodeReadIndex(); + //std::cout << "Index: " << index << std::endl; + output_vector[i] = output_function_.at(index).Add(); + } + + var_mgr_->dump_dot(output_vector, actor_labels, filename); +} + +} diff --git a/src/synthesis/source/synthesizer/ObligationLTLfPlusSynthesizer.cpp b/src/synthesis/source/synthesizer/ObligationLTLfPlusSynthesizer.cpp new file mode 100644 index 00000000..a9c0043f --- /dev/null +++ b/src/synthesis/source/synthesizer/ObligationLTLfPlusSynthesizer.cpp @@ -0,0 +1,645 @@ +// ObligationLTLfPlusSynthesizer.cpp +#include "/home/stella/LydiaSyft/src/synthesis/header/synthesizer/ObligationLTLfPlusSynthesizer.h" +#include "automata/ExplicitStateDfa.h" +#include "automata/ExplicitStateDfaAdd.h" +#include "lydia/logic/ltlfplus/base.hpp" +#include "lydia/logic/pnf.hpp" +#include "lydia/utils/print.hpp" +#include "lydia/mona_ext/mona_ext_base.hpp" +#include "lydia/dfa/mona_dfa.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + using BigInt = boost::multiprecision::cpp_int; + + std::string bigint_to_string(const std::optional& value) { + if (!value.has_value()) { + return "unknown"; + } + std::ostringstream oss; + oss << value.value(); + return oss.str(); + } + + /// Number of BDD bits needed to encode n states: ceil(log2(n)), minimum 1. + int bits_needed(int n) { + if (n <= 1) return 1; + // ceil(log2(n)) = floor(log2(n-1)) + 1 + int bits = 0; + int v = n - 1; + while (v > 0) { v >>= 1; ++bits; } + return bits; + } +} + +namespace Syft { + + /// Minimise an explicit DFA, but only keep the result if it actually + /// reduces the number of bits (ceil(log2(states))). Otherwise the + /// minimisation cost is wasted — the symbolic representation would use + /// the same number of BDD state variables either way. + static ExplicitStateDfa minimize_if_fewer_bits(ExplicitStateDfa dfa) { + int old_states = dfa.dfa_->ns; + int old_bits = bits_needed(old_states); + ExplicitStateDfa minimised = ExplicitStateDfa::dfa_minimize_weak(dfa); + int new_states = minimised.dfa_->ns; + int new_bits = bits_needed(new_states); + if (new_bits < old_bits) { + spdlog::debug("[ObligationFragment] Minimisation useful: {} states ({} bits) -> {} states ({} bits)", + old_states, old_bits, new_states, new_bits); + return minimised; + } + spdlog::debug("[ObligationFragment] Minimisation skipped: {} states ({} bits) -> {} states ({} bits), keeping original", + old_states, old_bits, new_states, new_bits); + return dfa; + } + + ObligationLTLfPlusSynthesizer::ObligationLTLfPlusSynthesizer( + LTLfPlus ltlf_plus_arg, + InputOutputPartition partition, + Actor starting_actor, + Actor protagonist_actor, + std::shared_ptr var_mgr, + bool use_balanced_boolean_product) + : ltlf_plus_formula_(std::move(ltlf_plus_arg)), + starting_actor_(starting_actor), + protagonist_actor_(protagonist_actor), + var_mgr_(var_mgr), + use_balanced_boolean_product_(use_balanced_boolean_product) { + // Attach the incoming LTLf+ argument to the member formula here. + + if (!var_mgr_) { + var_mgr_ = std::make_shared(); + auto env_vars = partition.input_variables; + auto agent_vars_groups = partition.agent_variables; + var_mgr_->create_named_variables(env_vars); + for (const auto& vars : agent_vars_groups) { + var_mgr_->create_named_variables(vars); + } + var_mgr_->partition_variables(env_vars, agent_vars_groups); + } +} + + void ObligationLTLfPlusSynthesizer::validate_obligation_fragment() const { + // Check each subformula to ensure it's in the obligation fragment + for (const auto& [formula, color] : ltlf_plus_formula_.formula_to_color_) { + // Convert back to ltlf_plus_ptr for the detector + // We need to check if the quantifier is obligation-compatible + auto quantifier = ltlf_plus_formula_.formula_to_quantification_.at(formula); + + // Only Forall and Exists are allowed in obligation fragment + if (quantifier != whitemech::lydia::PrefixQuantifier::Forall && + quantifier != whitemech::lydia::PrefixQuantifier::Exists) { + std::string error_msg = "Formula is not in obligation fragment. Found quantifier: "; + switch (quantifier) { + case whitemech::lydia::PrefixQuantifier::ForallExists: + error_msg += "ForallExists (recurrence)"; + break; + case whitemech::lydia::PrefixQuantifier::ExistsForall: + error_msg += "ExistsForall (persistence)"; + break; + default: + error_msg += "Unknown"; + break; + } + throw std::runtime_error(error_msg); + } + } + } + + // Helper struct to hold either explicit or symbolic DFA + struct HybridDfa { + std::optional explicit_dfa; + std::optional symbolic_dfa; + std::optional approx_state_count; // Optional approximation of number of states + bool is_symbolic; + std::shared_ptr var_mgr; + + // Constructor from explicit DFA + HybridDfa(ExplicitStateDfa e, std::shared_ptr vm) + : explicit_dfa(std::move(e)), symbolic_dfa(std::nullopt), + is_symbolic(false), var_mgr(vm) { + approx_state_count = BigInt(explicit_dfa->get_nb_states()); + } + + // Constructor from symbolic DFA (already converted) + HybridDfa(const SymbolicStateDfa& s, std::shared_ptr vm) + : explicit_dfa(std::nullopt), symbolic_dfa(s), + is_symbolic(true), var_mgr(vm) { + } + + std::optional state_count() const { + if (!is_symbolic) { + return BigInt(explicit_dfa->dfa_->ns); + } + return approx_state_count; + } + + std::string state_count_str() const { + return bigint_to_string(state_count()); + } + + void set_state_count(const BigInt& value) { + approx_state_count = value; + } + + void clear_state_count() { + approx_state_count.reset(); + } + + void convert_to_symbolic_if_needed(int symbolic_threshold) { + if (!is_symbolic && explicit_dfa->dfa_->ns > symbolic_threshold) { + spdlog::debug("[ObligationFragment] Converting to symbolic (exceeded threshold: {} > {})", + explicit_dfa->dfa_->ns, symbolic_threshold); + BigInt explicit_count = BigInt(explicit_dfa->get_nb_states()); + symbolic_dfa = SymbolicStateDfa::from_explicit( + ExplicitStateDfaAdd::from_dfa_mona(var_mgr, *explicit_dfa)); + is_symbolic = true; + approx_state_count = explicit_count; + explicit_dfa = std::nullopt; // Free the explicit DFA + } + } + + SymbolicStateDfa to_symbolic() { + if (!is_symbolic) { + BigInt explicit_count = BigInt(explicit_dfa->get_nb_states()); + symbolic_dfa = SymbolicStateDfa::from_explicit( + ExplicitStateDfaAdd::from_dfa_mona(var_mgr, *explicit_dfa)); + is_symbolic = true; + approx_state_count = explicit_count; + explicit_dfa = std::nullopt; // Free the explicit DFA + } + return *symbolic_dfa; + } + }; + + // Helper to parse color formula and build arena using hybrid approach + // Starts with explicit DFAs, automatically switches to symbolic when threshold exceeded + SymbolicStateDfa ObligationLTLfPlusSynthesizer::build_arena_from_color_formula_hybrid( + const std::string& color_formula, + const std::map& color_to_dfa) const { + + // Parse the color formula (e.g., "(1 & 2) | 3") and build DFA product using hybrid approach + // Simple recursive descent parser for: expr = term (('|' term)*) + // term = factor (('&' factor)*) + // factor = number | '(' expr ')' + + std::string formula = color_formula; + size_t pos = 0; + auto var_mgr = var_mgr_; // Capture var_mgr for lambdas + + std::function parse_expr; + std::function parse_term; + std::function parse_factor; + + auto skip_whitespace = [&]() { + while (pos < formula.size() && std::isspace(static_cast(formula[pos]))) { + ++pos; + } + }; + + auto combine_pair = [&](HybridDfa left, HybridDfa right, bool is_or) -> HybridDfa { + auto left_est = left.state_count(); + auto right_est = right.state_count(); + // Also switch to symbolic if product estimate exceeds threshold + auto estimated_product = std::optional(1); + if (left_est.has_value()) { + estimated_product.value() *= left_est.value(); + } else { + estimated_product = std::nullopt; + } + if (right_est.has_value()) { + estimated_product.value() *= right_est.value(); + } else { + estimated_product = std::nullopt; + } + if (left.is_symbolic || right.is_symbolic || (estimated_product.has_value() && estimated_product.value() > minimisation_options_.symbolic_threshold)) { + spdlog::debug("[ObligationFragment] Computing {} product using symbolic representation", + is_or ? "OR" : "AND"); + SymbolicStateDfa left_sym = left.to_symbolic(); + SymbolicStateDfa right_sym = right.to_symbolic(); + SymbolicStateDfa product = is_or + ? SymbolicStateDfa::product_OR({left_sym, right_sym}) + : SymbolicStateDfa::product_AND({left_sym, right_sym}); + HybridDfa combined(product, var_mgr_); + if (left_est && right_est) { + combined.set_state_count(left_est.value() * right_est.value()); + } else { + combined.clear_state_count(); + } + spdlog::debug("[ObligationFragment] Symbolic {} product computed", + is_or ? "OR" : "AND"); + spdlog::debug( + "[ObligationFragment] {} product combined ~{} with ~{} -> ~{}", + is_or ? "OR" : "AND", + bigint_to_string(left_est), + bigint_to_string(right_est), + combined.state_count_str()); + return combined; + } + + if (is_or) { + spdlog::debug("[ObligationFragment] Computing OR product using MONA"); + } else { + spdlog::debug("[ObligationFragment] Computing AND product using MONA"); + } + + ExplicitStateDfa product = is_or + ? ExplicitStateDfa::dfa_product_or({*left.explicit_dfa, *right.explicit_dfa}) + : ExplicitStateDfa::dfa_product_and({*left.explicit_dfa, *right.explicit_dfa}); + spdlog::debug("[ObligationFragment] {} product has {} states", + is_or ? "OR" : "AND", + product.dfa_->ns); + + if (product.dfa_->ns < minimisation_options_.threshold && minimisation_options_.allow_minimisation) { + spdlog::debug("[ObligationFragment] Attempting minimisation of {} product ({} states, threshold {})", + is_or ? "OR" : "AND", + product.dfa_->ns, + minimisation_options_.threshold); + product = minimize_if_fewer_bits(std::move(product)); + } + + HybridDfa combined(std::move(product), var_mgr_); + combined.convert_to_symbolic_if_needed(minimisation_options_.symbolic_threshold); + + spdlog::debug( + "[ObligationFragment] {} product combined ~{} with ~{} -> ~{}", + is_or ? "OR" : "AND", + bigint_to_string(left_est), + bigint_to_string(right_est), + combined.state_count_str()); + + return combined; + }; + + auto reduce_operands = [&](std::vector&& operands, bool is_or) -> HybridDfa { + if (operands.empty()) { + throw std::runtime_error("Empty operand list in color formula"); + } + if (operands.size() == 1) { + return std::move(operands.front()); + } + std::vector current = std::move(operands); + while (current.size() > 1) { + std::vector next; + next.reserve((current.size() + 1) / 2); + std::size_t i = 0; + for (; i + 1 < current.size(); i += 2) { + next.push_back(combine_pair(std::move(current[i]), std::move(current[i + 1]), is_or)); + } + if (i < current.size()) { + next.push_back(std::move(current.back())); + } + current = std::move(next); + } + return std::move(current.front()); + }; + + parse_factor = [&]() -> HybridDfa { + skip_whitespace(); + if (pos >= formula.size()) { + throw std::runtime_error("Unexpected end of color formula"); + } + + if (formula[pos] == '(') { + ++pos; + HybridDfa result = parse_expr(); + skip_whitespace(); + if (pos >= formula.size() || formula[pos] != ')') { + throw std::runtime_error("Expected ')' in color formula"); + } + ++pos; + return result; + } + if (std::isdigit(static_cast(formula[pos]))) { + size_t start = pos; + while (pos < formula.size() && std::isdigit(static_cast(formula[pos]))) { + ++pos; + } + int color = std::stoi(formula.substr(start, pos - start)); + + auto it = color_to_dfa.find(color); + if (it == color_to_dfa.end()) { + throw std::runtime_error("Unknown color in formula: " + std::to_string(color)); + } + return HybridDfa(it->second, var_mgr_); + } + + throw std::runtime_error("Unexpected character in color formula: " + std::string(1, formula[pos])); + }; + + parse_term = [&]() -> HybridDfa { + std::vector factors; + factors.push_back(parse_factor()); + while (true) { + skip_whitespace(); + if (pos < formula.size() && formula[pos] == '&') { + ++pos; + factors.push_back(parse_factor()); + } else { + break; + } + } + + if (use_balanced_boolean_product_) { + return reduce_operands(std::move(factors), false); + } + + HybridDfa accum = std::move(factors.front()); + for (std::size_t idx = 1; idx < factors.size(); ++idx) { + accum = combine_pair(std::move(accum), std::move(factors[idx]), false); + } + return accum; + }; + + parse_expr = [&]() -> HybridDfa { + std::vector terms; + terms.push_back(parse_term()); + while (true) { + skip_whitespace(); + if (pos < formula.size() && formula[pos] == '|') { + ++pos; + terms.push_back(parse_term()); + } else { + break; + } + } + + if (use_balanced_boolean_product_) { + return reduce_operands(std::move(terms), true); + } + + HybridDfa accum = std::move(terms.front()); + for (std::size_t idx = 1; idx < terms.size(); ++idx) { + accum = combine_pair(std::move(accum), std::move(terms[idx]), true); + } + return accum; + }; + + HybridDfa res = parse_expr(); + skip_whitespace(); + if (pos != formula.size()) { + throw std::runtime_error("Trailing characters in color formula after parsing"); + } + + //save explicit dfa into a mona file + if (!res.is_symbolic && res.explicit_dfa.has_value()) { + + std::string actor_name = protagonist_actor_.is_environment() ? "env" : "agent" + std::to_string(protagonist_actor_.id()); + std::string final_mona_path = "final_dfa_explicit_"+ actor_name +".mona"; + whitemech::lydia::print_mona_dfa( + res.explicit_dfa->dfa_, + final_mona_path, + res.explicit_dfa->get_nb_variables()); + //res.explicit_dfa->dfa_print(); + + + //spdlog::info("[Debug] Final explicit dfa saved in: {}", final_mona_path); + } else if (res.is_symbolic) { + spdlog::warn("[Debug] Impossible to print explicit dfa."); + } + + auto symbolic_arena = res.to_symbolic(); + // info_log the number of states we approximated using spdlog + //spdlog::info("[ObligationFragment] Final arena has approximately {} states, {} bits ", + // res.state_count_str(), + // symbolic_arena.transition_function().size()); + + // Always return symbolic representation + // Trigger CUDD variable reordering to compact BDDs after product construction + + return symbolic_arena; + } + + std::pair> + ObligationLTLfPlusSynthesizer::convert_to_symbolic_dfa() const { + using clock = std::chrono::high_resolution_clock; + auto t0 = clock::now(); + + // Step 1: Build all explicit DFAs with obligation transformations + std::map color_to_explicit_dfa; + std::map color_to_final_states; + + std::string actor_name = protagonist_actor_.is_environment() ? "env" : "agent" + std::to_string(protagonist_actor_.id()); + + //spdlog::info("[ObligationFragment] Building explicit DFAs for each color..."); + + for (const auto& [ltlf_plus_arg, prefix_quantifier] : ltlf_plus_formula_.formula_to_quantification_) { + whitemech::lydia::ltlf_ptr ltlf_arg = ltlf_plus_arg->ltlf_arg(); + ExplicitStateDfa explicit_dfa = ExplicitStateDfa::dfa_of_formula(*ltlf_arg); + + int color = std::stoi(ltlf_plus_formula_.formula_to_color_.at(ltlf_plus_arg)); + + switch (prefix_quantifier) { + + case whitemech::lydia::PrefixQuantifier::Forall: { + // Safety property: convert to G(phi) form + //spdlog::debug("[ObligationFragment] Applying Forall transformation for color {}", color); + ExplicitStateDfa trimmed_explicit_dfa = ExplicitStateDfa::dfa_to_Gdfa_obligation(explicit_dfa); + ExplicitStateDfa minised = minimize_if_fewer_bits(std::move(trimmed_explicit_dfa)); + color_to_explicit_dfa.insert({color, std::move(minised)}); + + // Optionally save the explicit DFA for debugging + // std::string filename = actor_name + "_color_" + std::to_string(color) + ".mona"; + // whitemech::lydia::print_mona_dfa( + // color_to_explicit_dfa.at(color).dfa_, // Il puntatore al DFA di MONA + // filename, // Nome file + // color_to_explicit_dfa.at(color).get_nb_variables() // Numero variabili + // ); + //spdlog::info("[Debug] Exported DFA MONA for color {} in {}", color, filename); + + break; + } + case whitemech::lydia::PrefixQuantifier::Exists: { + // Guarantee property: convert to F(phi) form + //spdlog::debug("[ObligationFragment] Applying Exists transformation for color {}", color); + Syft::ExplicitStateDfa trimmed_explicit_dfa = Syft::ExplicitStateDfa::dfa_to_Fdfa_obligation(explicit_dfa); + spdlog::debug("[ObligationFragment] Appplied Exists transformation for color {}", color); + Syft::ExplicitStateDfa minised = minimize_if_fewer_bits(std::move(trimmed_explicit_dfa)); + color_to_explicit_dfa.insert({color, std::move(minised)}); + + //Optionally save the explicit DFA for debugging + // std::string filename = actor_name + "_color_" + std::to_string(color)+ ".mona"; + // whitemech::lydia::print_mona_dfa( + // color_to_explicit_dfa.at(color).dfa_, // Il puntatore al DFA di MONA + // filename, // Nome file + // color_to_explicit_dfa.at(color).get_nb_variables() // Numero variabili + // ); + //spdlog::info("[Debug] Exported DFA MONA for color {} in {}", color, filename); + + break; + } + default: + // This should not happen since validate_obligation_fragment was called + throw std::runtime_error("Unexpected quantifier in obligation fragment conversion"); + } + } + + // Step 2: Build the product arena using hybrid approach (MONA when small, symbolic when large) + //spdlog::info("[ObligationFragment] Computing product DFA using hybrid approach..."); + SymbolicStateDfa arena = build_arena_from_color_formula_hybrid( + ltlf_plus_formula_.color_formula_, color_to_explicit_dfa); + + //spdlog::info("[ObligationFragment] Final arena DFA created"); + // Step 3: Collect final states for debugging (convert individual DFAs just for final state info) + for (const auto &[color, explicit_dfa] : color_to_explicit_dfa) { + ExplicitStateDfaAdd add = ExplicitStateDfaAdd::from_dfa_mona(var_mgr_, explicit_dfa); + SymbolicStateDfa symbolic = SymbolicStateDfa::from_explicit(std::move(add)); + color_to_final_states[color] = symbolic.final_states(); + } + + // the arena already encodes the combined finals in arena.final_states() + color_to_final_states[-1] = arena.final_states(); + + auto t1 = clock::now(); + auto ms = std::chrono::duration_cast(t1 - t0).count(); + //spdlog::info("[ObligationFragment] Total DFA construction time: {} ms", ms); + + return std::make_pair(arena, color_to_final_states); + } + + /* ELSynthesisResult ObligationLTLfPlusSynthesizer::solve_with_scc( + const SymbolicStateDfa& arena, + const std::map& color_to_final_states) const { + spdlog::info("[ObligationFragment] Solving with WeakGameSolver"); + + // Use the arena's final states which already encode the correct AND/OR structure + CUDD::BDD accepting_states = arena.final_states(); + + // Create and run the weak game solver (debug=true for detailed output) + WeakGameSolver solver(arena, accepting_states, true, starting_player_); + + // If a DFA dump path is set, dump the arena DFA to the file + if (!dfa_dump_path_.empty()) { + std::ofstream ofs(dfa_dump_path_); + if (ofs.is_open()) { + solver.DumpDFAForPython(ofs); + spdlog::info("[ObligationFragment] DFA dumped to {}", dfa_dump_path_); + } else { + spdlog::error("[ObligationFragment] Could not open {} for writing", dfa_dump_path_); + } + } + + WeakGameResult game_result = solver.Solve(); + + // Check if initial state is winning + CUDD::BDD initial_state = arena.initial_state_bdd(); + bool is_realizable = !(initial_state & !game_result.winning_states).IsZero() == false; + // Simplified: check if initial state is in winning states + is_realizable = !(initial_state & game_result.winning_states).IsZero(); + + spdlog::info("[ObligationFragment] Realizability: {}", (is_realizable ? "true" : "false")); + + // Build result + ELSynthesisResult result; + result.realizability = is_realizable; + result.winning_states = game_result.winning_states; + result.output_function = {}; // TODO: Extract strategy from winning_moves + result.z_tree = nullptr; + + return result; + + } + + ELSynthesisResult ObligationLTLfPlusSynthesizer::solve_with_buchi( + const SymbolicStateDfa& arena, + const std::map& color_to_final_states) const { + + spdlog::info("[ObligationFragment] Solving with BuchiStandalone solver"); + + // Use the arena's final states which already encode the correct AND/OR structure + CUDD::BDD accepting_states = arena.final_states(); + + // If a DFA dump path is set, dump the arena DFA to the file + if (!dfa_dump_path_.empty()) { + WeakGameSolver dump_solver(arena, accepting_states, false, starting_player_); + std::ofstream ofs(dfa_dump_path_); + if (ofs.is_open()) { + dump_solver.DumpDFAForPython(ofs); + spdlog::info("[ObligationFragment] DFA dumped to {}", dfa_dump_path_); + } else { + spdlog::error("[ObligationFragment] Could not open {} for writing", dfa_dump_path_); + } + } + + auto var_mgr = arena.var_mgr(); + auto mgr = var_mgr->cudd_mgr(); + auto automaton_id = arena.automaton_id(); + auto transition_func = arena.transition_function(); + auto initial_state = arena.initial_state_bdd(); + + CUDD::BDD state_space = initial_state; + CUDD::BDD current_layer = initial_state; + auto transition_vector = var_mgr->make_compose_vector(automaton_id, transition_func); + CUDD::BDD io_cube = var_mgr->input_cube() * var_mgr->output_cube(); + + + // Create and run the Büchi solver (arena already has final_states) + BuchiSolver solver(arena, starting_player_, protagonist_player_, var_mgr_->cudd_mgr()->bddOne(), buechi_mode_); + SynthesisResult game_result = solver.run(); + + spdlog::info("[ObligationFragment] BuchiStandalone completed"); + spdlog::info("[ObligationFragment] Realizability: {}", (game_result.realizability ? "true" : "false")); + + // Convert SynthesisResult to ELSynthesisResult + ELSynthesisResult result; + result.realizability = game_result.realizability; + result.winning_states = game_result.winning_states; + result.output_function = {}; // strategy extraction omitted + result.z_tree = nullptr; // not used here + + return result; + } + + ELSynthesisResult ObligationLTLfPlusSynthesizer::run() const { + // Step 1: Validate that the formula is in obligation fragment + validate_obligation_fragment(); + + // Step 2: Convert to symbolic state DFA + auto [arena, color_to_final_states] = convert_to_symbolic_dfa(); + + // If skip_synthesis is set, return an empty result after automata construction + if (skip_synthesis_) { + spdlog::info("[ObligationLTLfPlusSynthesizer::run] --skip-synthesis: " + "automata construction complete, skipping game solving."); + + // If a DFA dump path is set, dump the arena DFA before returning + if (!dfa_dump_path_.empty()) { + CUDD::BDD accepting_states = arena.final_states(); + WeakGameSolver solver(arena, accepting_states, false, starting_player_); + std::ofstream ofs(dfa_dump_path_); + if (ofs.is_open()) { + solver.DumpDFAForPython(ofs); + spdlog::info("[ObligationLTLfPlusSynthesizer::run] DFA dumped to {}", dfa_dump_path_); + } else { + spdlog::error("[ObligationLTLfPlusSynthesizer::run] Could not open {} for writing", dfa_dump_path_); + } + } + + ELSynthesisResult empty_result; + empty_result.realizability = false; + return empty_result; + } + + // Step 3: Solve using Büchi solver (replaces SCC/WeakGame path) + if (use_buchi_) { + return solve_with_buchi(arena, color_to_final_states); + } else { + return solve_with_scc(arena, color_to_final_states); + } + } */ + +} // namespace Syft \ No newline at end of file diff --git a/submodules/doxygen-awesome-css b/submodules/doxygen-awesome-css deleted file mode 160000 index 5b27b3a7..00000000 --- a/submodules/doxygen-awesome-css +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5b27b3a747ca1e559fa54149762cca0bad6036fb diff --git a/submodules/doxygen-awesome-css/.github/workflows/publish.yaml b/submodules/doxygen-awesome-css/.github/workflows/publish.yaml new file mode 100644 index 00000000..331822fd --- /dev/null +++ b/submodules/doxygen-awesome-css/.github/workflows/publish.yaml @@ -0,0 +1,22 @@ +name: publish +on: + release: + types: [published] +jobs: + deploy: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: set version + run: echo "PROJECT_NUMBER = `git describe --tags`" >> Doxyfile + - name: Generate Documentation + uses: mattnotmitt/doxygen-action@edge + - name: Publish generated content to GitHub Pages + uses: tsunematsu21/actions-publish-gh-pages@v1.0.2 + with: + dir: docs/html + branch: gh-pages + token: ${{ secrets.ACCESS_TOKEN }} \ No newline at end of file diff --git a/submodules/doxygen-awesome-css/.gitignore b/submodules/doxygen-awesome-css/.gitignore new file mode 100644 index 00000000..60db5bc3 --- /dev/null +++ b/submodules/doxygen-awesome-css/.gitignore @@ -0,0 +1,6 @@ +docs/html +.DS_Store +.idea + +node_modules +*.tgz diff --git a/submodules/doxygen-awesome-css/.npmignore b/submodules/doxygen-awesome-css/.npmignore new file mode 100644 index 00000000..90eb4cae --- /dev/null +++ b/submodules/doxygen-awesome-css/.npmignore @@ -0,0 +1,3 @@ +* +!doxygen-awesome* + diff --git a/submodules/doxygen-awesome-css/Doxyfile b/submodules/doxygen-awesome-css/Doxyfile new file mode 100644 index 00000000..860e32f6 --- /dev/null +++ b/submodules/doxygen-awesome-css/Doxyfile @@ -0,0 +1,2793 @@ +# Doxyfile 1.9.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Doxygen Awesome" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "Modern Doxygen theme" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = ./logo.drawio.svg + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = include \ + README.md \ + docs/extensions.md \ + docs/customization.md \ + docs/tricks.md + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = img \ + docs/img + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = README.md + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = doxygen-custom/header.html + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = doxygen-awesome.css \ + doxygen-custom/custom.css \ + doxygen-awesome-sidebar-only.css \ + doxygen-awesome-sidebar-only-darkmode-toggle.css \ + doxygen-custom/custom-alternative.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = doxygen-awesome-darkmode-toggle.js \ + doxygen-awesome-fragment-copy-button.js \ + doxygen-awesome-paragraph-link.js \ + doxygen-custom/toggle-alternative-theme.js \ + doxygen-awesome-interactive-toc.js \ + doxygen-awesome-tabs.js + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 209 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 255 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 113 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 335 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html +# #tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /