diff --git a/docs/algorithms/sidb_simulation.rst b/docs/algorithms/sidb_simulation.rst index 1aa5fdfe1..ef684bd12 100644 --- a/docs/algorithms/sidb_simulation.rst +++ b/docs/algorithms/sidb_simulation.rst @@ -10,9 +10,6 @@ are a crucial step in the physical design flow of SiDB layouts, as they are used .. doxygenstruct:: fiction::sidb_simulation_parameters :members: -.. doxgenfunction:: fiction::quicksim(const Lyt& lyt, const quicksim_params& ps = quicksim_params{}, quicksim_stats* pst = nullptr) - - **Header:** ``fiction/algorithms/simulation/sidb/quicksim.hpp`` .. doxygenstruct:: fiction::quicksim_params diff --git a/include/fiction/algorithms/simulation/sidb/is_ground_state.hpp b/include/fiction/algorithms/simulation/sidb/is_ground_state.hpp index 6da5e408c..0a8a49a5e 100644 --- a/include/fiction/algorithms/simulation/sidb/is_ground_state.hpp +++ b/include/fiction/algorithms/simulation/sidb/is_ground_state.hpp @@ -16,15 +16,13 @@ namespace fiction { /** - * This function checks if the ground state is found by the *quicksim* algorithm. + * This function checks if the ground state is found by the *QuickSim* algorithm. * * @tparam Lyt Cell-level layout type. - * @param quicksim_results All found physically valid charge distribution surfaces obtained by the quicksim algorithm - * (quicksim). - * @param exhaustive_results All valid charge distribution surfaces determined by ExGS - * (exhaustive_ground_state_simulation). - * @return Returns `true` if the relative difference between the lowest energies of the two sets is less than 0.00001, - * `false` otherwise. + * @param quicksim_results All found physically valid charge distribution surfaces obtained by the *QuickSim* algorithm. + * @param exhaustive_results All valid charge distribution surfaces determined by ExGS. + * @return Returns `true` if the relative difference between the lowest energies of the two sets is less than \f$ + * 0.00001 \f$, `false` otherwise. */ template [[nodiscard]] bool is_ground_state(const quicksim_stats& quicksim_results, diff --git a/include/fiction/algorithms/simulation/sidb/quicksim.hpp b/include/fiction/algorithms/simulation/sidb/quicksim.hpp index 468246963..83a0753e5 100644 --- a/include/fiction/algorithms/simulation/sidb/quicksim.hpp +++ b/include/fiction/algorithms/simulation/sidb/quicksim.hpp @@ -18,13 +18,15 @@ #include #include #include +#include +#include #include namespace fiction { /** - * This struct stores the parameters for the *quicksim* algorithm. + * This struct stores the parameters for the *QuickSim* algorithm. */ struct quicksim_params { @@ -37,43 +39,59 @@ struct quicksim_params */ uint64_t interation_steps{80}; /** - * Alpha parameter for the *quicksim* algorithm (should be reduced if no result is found). + * `alpha` parameter for the *QuickSim* algorithm (should be reduced if no result is found). */ double alpha{0.7}; + /** + * Number of threads to spawn. By default the number of threads is set to the number of available hardware threads. + */ + uint64_t number_threads{std::thread::hardware_concurrency()}; }; /** - * This struct stores the simulation runtime and all physically valid charge layouts gained by the quicksim algorithm. + * This struct stores the simulation runtime and all physically valid charge layouts gained by the *QuickSim* algorithm. * * @paramt Lyt Cell-level layout type. */ template struct quicksim_stats { - mockturtle::stopwatch<>::duration time_total{0}; + /** + * Total simulation runtime. + */ + mockturtle::stopwatch<>::duration time_total{0}; + /** + * Vector of all physically valid charge layouts. + */ std::vector> valid_lyts{}; - + /** + * Report the simulation statistics in a human-readable fashion. + * + * @param out Output stream to write to. + */ void report(std::ostream& out = std::cout) { - out << fmt::format("total time = {:.2f} secs\n", mockturtle::to_seconds(time_total)); + out << fmt::format("[i] total runtime: {:.2f} secs\n", mockturtle::to_seconds(time_total)); + if (!energy_distribution(valid_lyts).empty()) { for (auto [energy, count] : energy_distribution(valid_lyts)) { - out << fmt::format("[i] the lowest state energy is = {:.4f} \n", minimum_energy(valid_lyts)); - out << fmt::format("energy: {} | occurance: {} \n", energy, count); + out << fmt::format("[i] lowest energy state: {:.4f} meV \n", minimum_energy(valid_lyts)); + out << fmt::format("[i] energy: {} | occurrence: {} \n", energy, count); } } else { std::cout << "no state found" << std::endl; } + std::cout << "_____________________________________________________ \n"; } }; /** - * The *quicksim* algorithm is an electrostatic ground state simulation algorithm for SiDB layouts. It determines + * The *QuickSim* algorithm is an electrostatic ground state simulation algorithm for SiDB layouts. It determines * physically valid charge configurations (with minimal energy) of a given (already initialized) charge distribution * layout. Depending on the simulation parameters, the ground state is found with a certain probability after one run. * @@ -90,6 +108,7 @@ void quicksim(const Lyt& lyt, const quicksim_params& ps = quicksim_params{}, qui static_assert(has_sidb_technology_v, "Lyt must be an SiDB layout"); quicksim_stats st{}; + st.valid_lyts.reserve(ps.interation_steps); // measure run time (artificial scope) { @@ -100,8 +119,6 @@ void quicksim(const Lyt& lyt, const quicksim_params& ps = quicksim_params{}, qui // set the given physical parameters charge_lyt.set_physical_parameters(ps.phys_params); - std::vector> result{}; - charge_lyt.set_all_charge_states(sidb_charge_state::NEUTRAL); charge_lyt.update_after_charge_change(); @@ -118,30 +135,61 @@ void quicksim(const Lyt& lyt, const quicksim_params& ps = quicksim_params{}, qui st.valid_lyts.push_back(charge_distribution_surface{charge_lyt}); } - auto best_energy = std::numeric_limits::max(); - const auto bound = static_cast(std::round(0.6 * static_cast(charge_lyt.num_cells()))); - for (uint64_t z = 0u; z < ps.interation_steps; z++) + // If the number of threads is initially set to zero, the simulation is run with one thread. + const uint64_t num_threads = std::max(ps.number_threads, uint64_t{1}); + + // split the iterations among threads + const auto iter_per_thread = + std::max(ps.interation_steps / num_threads, + uint64_t{1}); // If the number of set threads is greater than the number of iterations, the + // number of threads defines how many times QuickSim is repeated + + // Only 60 % of all cells are used as the negatively charged cell in the first iteration step. + const auto upper_bound = static_cast(std::round(0.6 * static_cast(charge_lyt.num_cells()))); + + std::vector threads{}; + threads.reserve(num_threads); + std::mutex mutex{}; // used to control access to shared resources + + for (uint64_t z = 0ul; z < num_threads; z++) { - for (uint64_t i = 0u; i < bound; i++) - { - std::vector index_start{i}; - charge_lyt.set_all_charge_states(sidb_charge_state::NEUTRAL); - charge_lyt.assign_charge_state_by_cell_index(i, sidb_charge_state::NEGATIVE); - charge_lyt.update_local_potential(); - charge_lyt.recompute_system_energy(); - - const auto upper_limit = static_cast(static_cast(charge_lyt.num_cells()) / 1.5); - for (uint64_t num = 0; num < upper_limit; num++) + threads.emplace_back( + [&] { - charge_lyt.adjacent_search(ps.alpha, index_start); - charge_lyt.validity_check(); + charge_distribution_surface charge_lyt_copy{charge_lyt}; - if (charge_lyt.is_physically_valid() && (charge_lyt.get_system_energy() <= best_energy)) + for (uint64_t l = 0ul; l < iter_per_thread; ++l) { - st.valid_lyts.push_back(charge_distribution_surface{charge_lyt}); + for (uint64_t i = 0ul; i < upper_bound; ++i) + { + std::vector index_start{i}; + charge_lyt_copy.set_all_charge_states(sidb_charge_state::NEUTRAL); + charge_lyt_copy.assign_charge_state_by_cell_index(i, sidb_charge_state::NEGATIVE); + charge_lyt_copy.update_local_potential(); + charge_lyt_copy.set_system_energy_to_zero(); + + const auto upper_limit = + static_cast(static_cast(charge_lyt_copy.num_cells()) / 1.5); + + for (uint64_t num = 0ul; num < upper_limit; num++) + { + charge_lyt_copy.adjacent_search(ps.alpha, index_start); + charge_lyt_copy.validity_check(); + + if (charge_lyt_copy.is_physically_valid()) + { + const std::lock_guard lock{mutex}; + st.valid_lyts.push_back(charge_distribution_surface{charge_lyt_copy}); + } + } + } } - } - } + }); + } + + for (auto& thread : threads) + { + thread.join(); } } diff --git a/include/fiction/algorithms/simulation/sidb/time_to_solution.hpp b/include/fiction/algorithms/simulation/sidb/time_to_solution.hpp index 01dc058da..e8f5258be 100644 --- a/include/fiction/algorithms/simulation/sidb/time_to_solution.hpp +++ b/include/fiction/algorithms/simulation/sidb/time_to_solution.hpp @@ -26,7 +26,7 @@ namespace fiction /** * This struct stores the time-to-solution, the simulation accuracy and the average single simulation runtime of - * quicksim (see quicksim.hpp). + * *QuickSim* (see quicksim.hpp). * */ struct time_to_solution_stats @@ -61,15 +61,15 @@ struct time_to_solution_stats } }; /** - * This function determines the time-to-solution (TTS) and the accuracy (acc) of the *quicksim* algorithm. + * This function determines the time-to-solution (TTS) and the accuracy (acc) of the *QuickSim* algorithm. * * @tparam Lyt Cell-level layout type. * @param lyt Layout that is used for the simulation. * @param sidb_params Physical SiDB parameters which are used for the simulation. * @param ps Pointer to a struct where the results (time_to_solution, acc, single runtime) are stored. - * @param repetitions Number of repetitions to determine the simulation accuracy (repetitions = 100 ==> accuracy is - * precise to 1%). - * @param confidence_level The time-to-solution also depends one the given confidence level which can be set here. + * @param repetitions Number of repetitions to determine the simulation accuracy (`repetitions = 100` means that + * accuracy is precise to 1%). + * @param confidence_level The time-to-solution also depends on the given confidence level which can be set here. */ template void sim_acc_tts(const Lyt& lyt, const quicksim_params& quicksim_params, time_to_solution_stats* ps = nullptr, diff --git a/include/fiction/technology/charge_distribution_surface.hpp b/include/fiction/technology/charge_distribution_surface.hpp index af1799bd9..8745c7a4f 100644 --- a/include/fiction/technology/charge_distribution_surface.hpp +++ b/include/fiction/technology/charge_distribution_surface.hpp @@ -509,6 +509,13 @@ class charge_distribution_surface : public Lyt return std::nullopt; } + /** + * Sets the electrostatic system energy to zero. Can be used if only one SiDB is charged. + */ + void set_system_energy_to_zero() noexcept + { + strg->system_energy = 0.0; + } /** * Calculates the system's total electrostatic potential energy and stores it in the storage. */ @@ -706,15 +713,16 @@ class charge_distribution_surface : public Lyt this->index_to_charge_distribution(); } /** - * This function is used for the *quicksim* algorithm (see quicksim.hpp). It gets a vector with indices representing + * This function is used for the *QuickSim* algorithm (see quicksim.hpp). It gets a vector with indices representing * negatively charged SiDBs as input. Afterward, a distant and a neutrally charged SiDB is localized using a min-max - * diversity algorithm. This selected SiDB is set to "negativ" and the index is added to the input vector such that + * diversity algorithm. This selected SiDB is set to "negative" and the index is added to the input vector such that * the next iteration works correctly. * * @param alpha A parameter for the algorithm (default: 0.7). * @param negative_indices Vector of SiDBs indices that are already negatively charged (double occupied). */ void adjacent_search(const double alpha, std::vector& negative_indices) noexcept + { double dist_max = 0; const auto reserve_size = this->num_cells() - negative_indices.size(); diff --git a/libs/Catch2 b/libs/Catch2 index e8ba329b6..9ff3cde87 160000 --- a/libs/Catch2 +++ b/libs/Catch2 @@ -1 +1 @@ -Subproject commit e8ba329b6c66b06c4afd07953020b74c4b0f1a75 +Subproject commit 9ff3cde87b1d8c61d2d97c34bdc71f0ec0a34a9c diff --git a/test/algorithms/simulation/sidb/quicksim.cpp b/test/algorithms/simulation/sidb/quicksim.cpp index cb78f05ab..1df72e093 100644 --- a/test/algorithms/simulation/sidb/quicksim.cpp +++ b/test/algorithms/simulation/sidb/quicksim.cpp @@ -74,4 +74,92 @@ TEMPLATE_TEST_CASE( CHECK(!it.charge_exists(sidb_charge_state::POSITIVE)); } } + + SECTION("zero threads") + { + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({4, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({6, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({6, 10, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 10, 0}, TestType::cell_type::NORMAL); + + quicksim_stats quicksimstats{}; + const sidb_simulation_parameters params{2, -0.30}; + const quicksim_params quicksim_params{params, 80, 0.7, 0}; + quicksim(lyt, quicksim_params, &quicksimstats); + CHECK(!quicksimstats.valid_lyts.empty()); + CHECK(quicksimstats.time_total.count() > 0); + } + + SECTION("one thread") + { + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({4, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({6, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({6, 10, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 10, 0}, TestType::cell_type::NORMAL); + + quicksim_stats quicksimstats{}; + const sidb_simulation_parameters params{2, -0.30}; + const quicksim_params quicksim_params{params, 80, 0.7, 1}; + quicksim(lyt, quicksim_params, &quicksimstats); + CHECK(!quicksimstats.valid_lyts.empty()); + CHECK(quicksimstats.time_total.count() > 0); + } + + SECTION("two threads") + { + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({4, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({6, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({6, 10, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 10, 0}, TestType::cell_type::NORMAL); + + quicksim_stats quicksimstats{}; + const sidb_simulation_parameters params{2, -0.30}; + const quicksim_params quicksim_params{params, 80, 0.7, 2}; + quicksim(lyt, quicksim_params, &quicksimstats); + CHECK(!quicksimstats.valid_lyts.empty()); + CHECK(quicksimstats.time_total.count() > 0); + } + + SECTION("100 threads") + { + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({4, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({6, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({6, 10, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 10, 0}, TestType::cell_type::NORMAL); + + quicksim_stats quicksimstats{}; + const sidb_simulation_parameters params{2, -0.30}; + const quicksim_params quicksim_params{params, 80, 0.7, 100}; + quicksim(lyt, quicksim_params, &quicksimstats); + CHECK(!quicksimstats.valid_lyts.empty()); + CHECK(quicksimstats.time_total.count() > 0); + } }