diff --git a/docs/algorithms/iterators.rst b/docs/algorithms/iterators.rst index 80e1c6162..b836370fe 100644 --- a/docs/algorithms/iterators.rst +++ b/docs/algorithms/iterators.rst @@ -5,3 +5,12 @@ Aspect Ratio Iterator .. doxygenclass:: fiction::aspect_ratio_iterator :members: + + +Gray Code Iterator +------------------ + +**Header:** ``fiction/algorithms/iter/gray_code_iterator.hpp`` + +.. doxygenclass:: fiction::gray_code_iterator + :members: diff --git a/docs/algorithms/sidb_simulation.rst b/docs/algorithms/sidb_simulation.rst index f044c4266..e9632ac0e 100644 --- a/docs/algorithms/sidb_simulation.rst +++ b/docs/algorithms/sidb_simulation.rst @@ -1,8 +1,7 @@ -SiDB Electrostatic Ground State Simulation ------------------------------------------- +Physical Simulation of Silicon Dangling Bond Logic +-------------------------------------------------- -These headers provide functions for physically simulating the *ground state* of an SiDB layout. Ground state simulations -are a crucial step in the physical design flow of SiDB layouts, as they are used to validate their functionality. +These headers provide functions for physically simulating an SiDB layout, which is a crucial step in the physical design flow of SiDB layouts, as they are used to validate their functionality. Physical Parameters @@ -39,6 +38,13 @@ Heuristic Ground State Simulation Exhaustive Ground State Simulation ################################## +**Header:** ``fiction/algorithms/simulation/sidb/quickexact.hpp`` + +.. doxygenenum:: fiction::automatic_base_number_detection +.. doxygenstruct:: fiction::quickexact_params + :members: +.. doxygenfunction:: fiction::quickexact + **Header:** ``fiction/algorithms/simulation/sidb/exhaustive_ground_state_simulation.hpp`` .. doxygenfunction:: fiction::exhaustive_ground_state_simulation @@ -87,12 +93,24 @@ Temperature Behavior .. doxygenfunction:: fiction::calculate_energy_and_state_type +Maximum Defect Influence Distance +################################# + +**Header:** ``fiction/algorithms/simulation/sidb/maximum_defect_influence_position_and_distance.hpp`` + +.. doxygenstruct:: fiction::maximum_defect_influence_distance_params + :members: +.. doxygenfunction:: fiction::maximum_defect_influence_position_and_distance + Time-to-Solution (TTS) Statistics ################################# **Header:** ``fiction/algorithms/simulation/sidb/time_to_solution.hpp`` +.. doxygenenum:: fiction::exhaustive_algorithm +.. doxygenstruct:: fiction::time_to_solution_params + :members: .. doxygenfunction:: fiction::sim_acc_tts diff --git a/include/fiction/algorithms/iter/gray_code_iterator.hpp b/include/fiction/algorithms/iter/gray_code_iterator.hpp new file mode 100644 index 000000000..3387fcb70 --- /dev/null +++ b/include/fiction/algorithms/iter/gray_code_iterator.hpp @@ -0,0 +1,341 @@ +// +// Created by Jan Drewniok on 30.08.23. +// + +#ifndef FICTION_GRAY_CODE_ITERATOR_HPP +#define FICTION_GRAY_CODE_ITERATOR_HPP + +#include +#include +#include + +namespace fiction +{ +/** + * An iterator type that iterates over Gray code representations for decimal numbers. + * + * The `gray_code_iterator` class provides an iterator for generating Gray code representations + * for a range of decimal numbers. It starts from a specified number and produces Gray codes + * in ascending order based on bitwise XOR operations. + */ +class gray_code_iterator +{ + public: + /** + * Constructs a Gray Code Iterator with a specified starting number. + * + * Constructs a `gray_code_iterator` that generates Gray codes for decimal numbers + * starting from the given `start` number. + * + * @param start The starting decimal number for the iterator. + */ + explicit constexpr gray_code_iterator(const uint64_t start) noexcept : + start_number{start}, + current_iteration{start}, + current_gray_code{start} + { + binary_to_gray(); + }; + /** + * Dereference operator. Returns a reference to the Gray code of the current iteration. + * + * @return Reference to the current Gray code. + */ + [[nodiscard]] constexpr const uint64_t& operator*() const noexcept + { + return current_gray_code; + } + /** + * Prefix increment operator. Sets the number and the corresponding Gray code. + * + * @return Reference to `this`. + */ + constexpr gray_code_iterator& operator++() noexcept + { + ++current_iteration; + binary_to_gray(); + + return *this; + } + /** + * Equality comparison operator. Compares the current iterator with another iterator. + * + * @param other The iterator to compare with. + * @return `true` if the current iterator is equal to the other iterator, `false` otherwise. + */ + [[nodiscard]] constexpr bool operator==(const gray_code_iterator& other) const noexcept + { + return (current_gray_code == other.current_gray_code); + } + /** + * Inequality comparison operator. Compares the current iterator with another iterator. + * + * @param other The iterator to compare with. + * @return `true` if the current iterator is not equal to the other iterator, `false` otherwise. + */ + [[nodiscard]] constexpr bool operator!=(const gray_code_iterator& other) const noexcept + { + return !(*this == other); + } + /** + * Less-than comparison operator. Compares the current iterator with another iterator. + * + * @param other The iterator to compare with. + * @return `true` if the current iterator is less than the other iterator, `false` otherwise. + */ + [[nodiscard]] constexpr bool operator<(const gray_code_iterator& other) const noexcept + { + return current_gray_code < other.current_gray_code; + } + /** + * Less-than or equal-to comparison operator. Compares the current iterator with another iterator. + * + * @param other The iterator to compare with. + * @return `true` if the current iterator is less than or equal to the other iterator, `false` otherwise. + */ + [[nodiscard]] constexpr bool operator<=(const gray_code_iterator& other) const noexcept + { + return current_gray_code <= other.current_gray_code; + } + /** + * Subtraction operator to calculate the difference between two gray_code_iterators. + * + * This operator calculates the difference between the current iterator and another + * gray_code_iterator provided as input. The result is returned as an int64_t representing + * the number of positions between the iterators. + * + * @param other The gray_code_iterator to subtract from the current iterator. + * @return The difference between the current iterator and the input iterator as int64_t. + */ + constexpr int64_t operator-(const gray_code_iterator& other) const noexcept + { + return static_cast(current_iteration) - static_cast(other.current_iteration); + } + /** + * Postfix increment operator. Sets the next Gray Code. + * + * @return Copy of `this` before incrementing. + */ + constexpr gray_code_iterator operator++(int) noexcept + { + auto result{*this}; + + ++(*this); + + return result; + } + /** + * Addition operator. Computes the Gray code of the current iterator plus the given integer. + * + * @param m The amount of Gray codes to skip. + * @return Iterator of the current iterator plus the given integer. + */ + [[nodiscard]] constexpr gray_code_iterator operator+(const int m) const noexcept + { + auto result{*this}; + + result += m; + + return result; + } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" + /** + * Addition assignment operator. Iterator is increased by given number. + * + * @param m The amount of Gray codes to skip. + * @return Reference to `this`. + */ + constexpr gray_code_iterator& operator+=(const int m) noexcept + { + current_iteration += m; + binary_to_gray(); + + return *this; + } +#pragma GCC diagnostic pop + /** + * Subtraction operator. Computes the Gray code of the current iterator minus the given integer. + * + * @param m The amount of Gray codes to skip. + * @return Iterator of the current iterator minus the given integer. + */ + [[nodiscard]] constexpr gray_code_iterator operator-(const int m) const noexcept + { + auto result{*this}; + + result -= m; + + return result; + } + /** + * Prefix decrement operator. Sets the previous Gray code. + * + * @return Reference to `this`. + */ + constexpr gray_code_iterator& operator--() noexcept + { + --current_iteration; + + binary_to_gray(); + + return *this; + } + /** + * Postfix decrement operator. Sets the previous Gray Code. + * + * @return Copy of `this` before decrementing. + */ + constexpr gray_code_iterator operator--(int) noexcept + { + auto result{*this}; + + --(*this); + + return result; + } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" + /** + * Subtraction assignment operator. Sets a previous Gray code. + * + * @param m The amount of Gray codes to skip. + * @return Reference to `this`. + */ + constexpr gray_code_iterator& operator-=(const int m) noexcept + { + current_iteration -= m; + + binary_to_gray(); + + return *this; + } +#pragma GCC diagnostic pop + /** + * Assignment operator. Sets the current number to the given integer. + * + * @param m The number to set. + */ + constexpr gray_code_iterator& operator=(const uint64_t m) noexcept + { + current_iteration = m; + binary_to_gray(); + return *this; + } + /** + * Subscript operator. Returns the Gray code at a specific position in the iteration range. + * + * @param index The position in the iteration range. + * @return The Gray code at the specified position. + */ + constexpr uint64_t operator[](size_t index) const noexcept + { + // Calculate the Gray code at the specified position + uint64_t result = start_number + static_cast(index); + result ^= (result >> 1u); + return result; + } + /** + * Equality operator. Compares the current number with the given integer. + * + * @param m Integer to compare with. + * @return `true` if the current number is equal to `m`, `false` otherwise. + */ + [[nodiscard]] constexpr bool operator==(const uint64_t m) const noexcept + { + return current_iteration == m; + } + /** + * Inequality operator. Compares the current number with the given integer. + * + * @param m Integer to compare with. + * @return `true` if the current number is not equal to `m`, `false` otherwise. + */ + [[nodiscard]] constexpr bool operator!=(const uint64_t m) const noexcept + { + return current_iteration != m; + } + /** + * Less-than operator. Compares the current number with the given integer. + * + * @param m Integer to compare with. + * @return `true` if the current number is less than `m`, `false` otherwise. + */ + [[nodiscard]] constexpr bool operator<(const uint64_t m) const noexcept + { + return current_iteration < m; + } + /** + * Less-or-equal-than operator. Compares the current number with the given integer. + * + * @param m Integer to compare with. + * @return `true` if the current number is less than or equal to `m`, `false` otherwise. + */ + [[nodiscard]] constexpr bool operator<=(const uint64_t m) const noexcept + { + return current_iteration <= m; + } + /** + * Greater-than operator. Compares the current number with the given integer. + * + * @param m Integer to compare with. + * @return `true` if the current number is greater than `m`, `false` otherwise. + */ + [[nodiscard]] constexpr bool operator>(const uint64_t m) const noexcept + { + return current_iteration > m; + } + /** + * Greater-or-equal-than operator. Compares the current number with the given integer. + * + * @param m Integer to compare with. + * @return `true` if the current number is greater than or equal to `m`, `false` otherwise. + */ + [[nodiscard]] constexpr bool operator>=(const uint64_t m) const noexcept + { + return current_iteration >= m; + } + + private: + /** + * Start number of the iteration. + */ + const uint64_t start_number; + /** + * Current number (i.e., current iteration number). + */ + uint64_t current_iteration; + /** + * Current Gray Code. + */ + uint64_t current_gray_code; + /** + * Converts the current decimal number into its corresponding Gray code representation. + * + * This function operates on the current decimal number and produces its Gray code + * representation using the bitwise XOR operation. Gray code is a binary numeral system + * in which two successive values differ in only one bit. + * + * The result is stored in the 'current_gray_code' variable. + */ + constexpr void binary_to_gray() noexcept + { + current_gray_code = current_iteration ^ (current_iteration >> 1u); + } +}; + +} // namespace fiction + +// make `gray_code_iterator` compatible with STL iterator categories +namespace std +{ +template <> +struct iterator_traits +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = uint64_t; + using difference_type = int64_t; +}; +} // namespace std + +#endif // FICTION_GRAY_CODE_ITERATOR_HPP diff --git a/include/fiction/algorithms/simulation/sidb/can_positive_charges_occur.hpp b/include/fiction/algorithms/simulation/sidb/can_positive_charges_occur.hpp new file mode 100644 index 000000000..d876b28ad --- /dev/null +++ b/include/fiction/algorithms/simulation/sidb/can_positive_charges_occur.hpp @@ -0,0 +1,56 @@ +// +// Created by Jan Drewniok on 12.08.23. +// + +#ifndef FICTION_CAN_POSITIVE_CHARGES_OCCUR_HPP +#define FICTION_CAN_POSITIVE_CHARGES_OCCUR_HPP + +#include "fiction/algorithms/simulation/sidb/sidb_simulation_parameters.hpp" +#include "fiction/technology/charge_distribution_surface.hpp" +#include "fiction/traits.hpp" + +#include + +namespace fiction +{ +/** + * This algorithm determines if positively charged SiDBs can occur in a given SiDB cell-level layout due to strong + * electrostatic interaction. + * + * @tparam Lyt SiDB cell-level layout type. + * @param lyt The layout to be analyzed. + * @param sim_params Physical parameters used to determine whether positively charged SiDBs can occur. + */ +template +bool can_positive_charges_occur(const Lyt& lyt, const sidb_simulation_parameters& sim_params) +{ + static_assert(is_cell_level_layout_v, "Lyt is not a cell-level layout"); + static_assert(has_sidb_technology_v, "Lyt is not an SiDB layout"); + + bool result = false; + const auto mu_plus = sim_params.mu_plus(); + + // The charge layout is initialized with negatively charged SiDBs. Therefore, the local electrostatic potentials are + // maximal. In this extreme case, if the banding is not sufficient for any SiDB to be positively charged, it will + // not be for any other charge distribution. Therefore, no positively charged SiDBs can occur. + const charge_distribution_surface charge_lyt{lyt, sim_params, sidb_charge_state::NEGATIVE}; + charge_lyt.foreach_cell( + [&result, &mu_plus, charge_lyt](const auto& c) + { + if (const auto local_pot = charge_lyt.get_local_potential(c); local_pot.has_value()) + { + if ((-(*local_pot) + mu_plus) > -physical_constants::POP_STABILITY_ERR) + { + result = true; + return false; // break + } + } + return true; + }); + + return result; +} + +} // namespace fiction + +#endif // FICTION_CAN_POSITIVE_CHARGES_OCCUR_HPP diff --git a/include/fiction/algorithms/simulation/sidb/critical_temperature.hpp b/include/fiction/algorithms/simulation/sidb/critical_temperature.hpp index 97a5ab43a..56d07f518 100644 --- a/include/fiction/algorithms/simulation/sidb/critical_temperature.hpp +++ b/include/fiction/algorithms/simulation/sidb/critical_temperature.hpp @@ -9,6 +9,7 @@ #include "fiction/algorithms/simulation/sidb/energy_distribution.hpp" #include "fiction/algorithms/simulation/sidb/exhaustive_ground_state_simulation.hpp" #include "fiction/algorithms/simulation/sidb/occupation_probability_of_excited_states.hpp" +#include "fiction/algorithms/simulation/sidb/quickexact.hpp" #include "fiction/algorithms/simulation/sidb/quicksim.hpp" #include "fiction/algorithms/simulation/sidb/sidb_simulation_result.hpp" #include "fiction/technology/cell_technologies.hpp" @@ -180,10 +181,12 @@ class critical_temperature_impl sidb_simulation_result simulation_results{}; if (parameter.engine == simulation_engine::EXACT) { - temperature_stats.algorithm_name = "ExGS"; - // All physically valid charge configurations are determined for the given layout (exhaustive ground state - // simulation is used to provide 100 % accuracy for the Critical Temperature). - simulation_results = exhaustive_ground_state_simulation(layout, parameter.simulation_params.phys_params); + temperature_stats.algorithm_name = "QuickExact"; + // All physically valid charge configurations are determined for the given layout (`QuickExact` simulation + // is used to provide 100 % accuracy for the Critical Temperature). + const quickexact_params params{parameter.simulation_params.phys_params, + automatic_base_number_detection::OFF}; + simulation_results = quickexact(layout, params); } else { @@ -213,6 +216,17 @@ class critical_temperature_impl // The goal is to sort the cells from left to right and top to bottom. std::sort(all_cells.begin(), all_cells.end()); + auto lowest_energy = round_to_n_decimal_places(minimum_energy(simulation_results.charge_distributions), 6); + charge_distribution_surface lyt_copy{}; + for (const auto& lyt : simulation_results.charge_distributions) + { + if (std::fabs(round_to_n_decimal_places(lyt.get_system_energy(), 6) - lowest_energy) < + std::numeric_limits::epsilon()) + { + lyt_copy = charge_distribution_surface{lyt}; + } + } + // The energy distribution of the physically valid charge configurations for the given layout is determined. const auto distribution = energy_distribution(simulation_results.charge_distributions); @@ -224,8 +238,8 @@ class critical_temperature_impl { if (parameter.truth_table.num_bits() == 8) // number of bits of truth table. { - output_bits_index = {-4, -3}; // double wire, cx, etc. - // Truth table entries for given inputs are collected. + output_bits_index = {-4, -3}; + // double-wire, cx, etc Truth table entries for given inputs are collected. output_bits.push_back(kitty::get_bit(parameter.truth_table, parameter.input_bit * 2 + 1) != 0u); output_bits.push_back(kitty::get_bit(parameter.truth_table, parameter.input_bit * 2) != 0u); } @@ -233,8 +247,8 @@ class critical_temperature_impl else if (parameter.truth_table.num_vars() == 1 && parameter.truth_table.num_bits() == 2) { - output_bits_index = {-2}; // Wire, inverter, etc. -2 due to placed perturber. - // Truth table entry for given input is collected. + output_bits_index = {-2}; + // Wire, inverter, etc. -2 due to placed perturber. Truth table entry for given input is collected. output_bits.push_back(kitty::get_bit(parameter.truth_table, parameter.input_bit) != 0u); } @@ -243,14 +257,14 @@ class critical_temperature_impl if (parameter.truth_table.num_bits() == 4 && parameter.truth_table != create_fan_out_tt()) // and, or, nand, etc. { - output_bits_index = {-2}; // One output SiDB. -2 due to placed perturber. - // Truth table entry for given inputs is collected. + output_bits_index = {-2}; + // One output SiDB. -2 due to placed perturber. Truth table entry for given inputs is collected. output_bits.push_back(kitty::get_bit(parameter.truth_table, parameter.input_bit) != 0u); } else { output_bits_index = {-4, -3}; // fo2. - // Truth table entries for given input is collected. + // Truth table entries for given input are collected. output_bits.push_back(kitty::get_bit(parameter.truth_table, parameter.input_bit * 2 + 1) != 0u); output_bits.push_back(kitty::get_bit(parameter.truth_table, parameter.input_bit * 2) != 0u); } @@ -297,14 +311,16 @@ class critical_temperature_impl sidb_simulation_result simulation_results{}; if (parameter.engine == simulation_engine::EXACT) { - temperature_stats.algorithm_name = "exgs"; - // All physically valid charge configurations are determined for the given layout (exhaustive ground state - // simulation is used to provide 100 % accuracy for the Critical Temperature). - simulation_results = exhaustive_ground_state_simulation(layout, parameter.simulation_params.phys_params); + temperature_stats.algorithm_name = "QuickExact"; + // All physically valid charge configurations are determined for the given layout (`QuickExact` simulation + // is used to provide 100 % accuracy for the Critical Temperature). + const quickexact_params params{parameter.simulation_params.phys_params, + automatic_base_number_detection::OFF}; + simulation_results = quickexact(layout, params); } else { - temperature_stats.algorithm_name = "quicksim"; + temperature_stats.algorithm_name = "QuickSim"; // All physically valid charge configurations are determined for the given layout (exhaustive ground state // simulation is used to provide 100 % accuracy for the Critical Temperature). simulation_results = quicksim(layout, parameter.simulation_params); @@ -428,7 +444,7 @@ class critical_temperature_impl /** * SiDB cell-level layout. */ - const Lyt& layout{}; + Lyt layout{}; /** * Parameters for the `critical_temperature` algorithm. */ diff --git a/include/fiction/algorithms/simulation/sidb/enum_class_exhaustive_algorithm.hpp b/include/fiction/algorithms/simulation/sidb/enum_class_exhaustive_algorithm.hpp new file mode 100644 index 000000000..b3e75f232 --- /dev/null +++ b/include/fiction/algorithms/simulation/sidb/enum_class_exhaustive_algorithm.hpp @@ -0,0 +1,28 @@ +// +// Created by Jan Drewniok on 06.06.23. +// + +#ifndef FICTION_ENUM_CLASS_EXHAUSTIVE_ALGORITHM_HPP +#define FICTION_ENUM_CLASS_EXHAUSTIVE_ALGORITHM_HPP + +namespace fiction +{ + +/** + * An enumeration of exact algorithms for the TTS-simulation. + */ +enum class exhaustive_algorithm +{ + /** + * ExGS + */ + EXGS, + /** + * QuickExact + */ + QUICKEXACT +}; + +} // namespace fiction + +#endif // FICTION_ENUM_CLASS_EXHAUSTIVE_ALGORITHM_HPP diff --git a/include/fiction/algorithms/simulation/sidb/exhaustive_ground_state_simulation.hpp b/include/fiction/algorithms/simulation/sidb/exhaustive_ground_state_simulation.hpp index 093e16ffd..2b61ec231 100644 --- a/include/fiction/algorithms/simulation/sidb/exhaustive_ground_state_simulation.hpp +++ b/include/fiction/algorithms/simulation/sidb/exhaustive_ground_state_simulation.hpp @@ -21,7 +21,13 @@ namespace fiction { /** - * All metastable and physically valid charge distribution layouts are computed, stored in a vector and returned. + * This algorithm computes all physically valid charge configurations of a given SiDB layout. All possible charge + * configurations are passed and checked for physical validity. As a consequence, its runtime grows exponentially with + * the number of SiDBs per layout. Therefore, only layouts with up to 30 DBs can be simulated in a reasonable time. + * However, since all charge configurations are checked for validity, 100 % simulation accuracy is guaranteed. + * + * @note This was the first exact simulation approach. However, it is replaced by `QuickExact` due to the much + * better runtimes and more functionality. * * @tparam Lyt Cell-level layout type. * @param lyt The layout to simulate. @@ -46,11 +52,11 @@ exhaustive_ground_state_simulation(const Lyt& lyt, charge_distribution_surface charge_lyt{lyt}; - charge_lyt.set_physical_parameters(params); - charge_lyt.set_all_charge_states(sidb_charge_state::NEGATIVE); + charge_lyt.assign_physical_parameters(params); + charge_lyt.assign_all_charge_states(sidb_charge_state::NEGATIVE); charge_lyt.update_after_charge_change(); - while (charge_lyt.get_charge_index().first < charge_lyt.get_max_charge_index()) + while (charge_lyt.get_charge_index_and_base().first < charge_lyt.get_max_charge_index()) { if (charge_lyt.is_physically_valid()) diff --git a/include/fiction/algorithms/simulation/sidb/is_ground_state.hpp b/include/fiction/algorithms/simulation/sidb/is_ground_state.hpp index a1696688a..ddbcd0424 100644 --- a/include/fiction/algorithms/simulation/sidb/is_ground_state.hpp +++ b/include/fiction/algorithms/simulation/sidb/is_ground_state.hpp @@ -5,8 +5,8 @@ #ifndef FICTION_IS_GROUND_STATE_HPP #define FICTION_IS_GROUND_STATE_HPP -#include "fiction/algorithms/simulation/sidb/exhaustive_ground_state_simulation.hpp" #include "fiction/algorithms/simulation/sidb/minimum_energy.hpp" +#include "fiction/algorithms/simulation/sidb/quickexact.hpp" #include "fiction/algorithms/simulation/sidb/quicksim.hpp" #include "fiction/algorithms/simulation/sidb/sidb_simulation_result.hpp" #include "fiction/traits.hpp" diff --git a/include/fiction/algorithms/simulation/sidb/maximum_defect_influence_position_and_distance.hpp b/include/fiction/algorithms/simulation/sidb/maximum_defect_influence_position_and_distance.hpp new file mode 100644 index 000000000..35194369f --- /dev/null +++ b/include/fiction/algorithms/simulation/sidb/maximum_defect_influence_position_and_distance.hpp @@ -0,0 +1,260 @@ +// +// Created by Jan Drewniok on 21.06.23. +// + +#ifndef FICTION_MAXIMUM_DEFECT_INFLUENCE_POSITION_AND_DISTANCE_HPP +#define FICTION_MAXIMUM_DEFECT_INFLUENCE_POSITION_AND_DISTANCE_HPP + +#include "fiction/algorithms/simulation/sidb/critical_temperature.hpp" +#include "fiction/algorithms/simulation/sidb/quickexact.hpp" +#include "fiction/layouts/bounding_box.hpp" +#include "fiction/technology/sidb_defects.hpp" +#include "fiction/technology/sidb_surface.hpp" +#include "fiction/types.hpp" +#include "fiction/utils/execution_utils.hpp" +#include "fiction/utils/layout_utils.hpp" + +#include +#include +#include +#include +#include + +namespace fiction +{ +/** + * This struct stores the parameters for the `maximum_defect_influence_position_and_distance` algorithm. + */ +struct maximum_defect_influence_distance_params +{ + /** + * The defect to calculate the maximum defect influence distance for. + */ + sidb_defect defect{}; + /** + * Physical simulation parameters. + */ + sidb_simulation_parameters physical_params{}; + /** + * The pair describes the width and height of the area around the gate, which is + * also used to place defects. + * + * @note The height (second entry of the pair) of the area is given in the y coordinate of the SiQAD coordinates. + * This means that it describes the number of dimer rows. + */ + std::pair additional_scanning_area{50, 6}; +}; + +namespace detail +{ +/** + * A class for simulating the maximum influence distance of defects within an SiDB layout. + * + * This class is responsible for simulating the distance at which defects placed within an SiDB + * layout still influence the ground state of the layout. It conducts simulations at various defect positions, + * identifying the position that maximally impacts the layout and calculating the associated influence distance. + * + * The class provides a `run` method to initiate the simulation and compute the maximum influence + * distance and corresponding defect position. It utilizes multithreading for efficient defect + * position simulations. + */ +template +class maximum_defect_influence_position_and_distance_impl +{ + public: + maximum_defect_influence_position_and_distance_impl(const Lyt& lyt, + const maximum_defect_influence_distance_params& sim_params) : + layout{lyt}, + params{sim_params} + { + collect_all_defect_cells(); + } + + std::pair run() noexcept + { + const quickexact_params> params_defect{params.physical_params, + automatic_base_number_detection::OFF}; + + double avoidance_distance{0}; + coordinate max_defect_position{}; + + const auto simulation_results = + quickexact(layout, quickexact_params{params.physical_params, automatic_base_number_detection::OFF}); + + const auto min_energy = minimum_energy(simulation_results.charge_distributions); + uint64_t charge_index_layout = 0; + + for (auto& lyt_result : simulation_results.charge_distributions) + { + if (std::fabs(round_to_n_decimal_places(lyt_result.get_system_energy(), 6) - + round_to_n_decimal_places(min_energy, 6)) < std::numeric_limits::epsilon()) + { + lyt_result.charge_distribution_to_index_general(); + charge_index_layout = lyt_result.get_charge_index_and_base().first; + } + } + + // simulate the impact of the defect at a given position on the ground state of the SiDB layout + const auto process_defect = [&](const auto& defect) noexcept + { + sidb_surface lyt_defect{}; + + layout.foreach_cell([this, &lyt_defect](const auto& cell) + { lyt_defect.assign_cell_type(cell, layout.get_cell_type(cell)); }); + + // assign defect to layout + lyt_defect.assign_sidb_defect(defect, params.defect); + // conduct simulation with defect + auto simulation_result_defect = quickexact(lyt_defect, params_defect); + + const auto min_energy_defect = minimum_energy(simulation_result_defect.charge_distributions); + uint64_t charge_index_defect_layout = 0; + + // get the charge index of the ground state + for (const auto& lyt_simulation_with_defect : simulation_result_defect.charge_distributions) + { + if (std::fabs(round_to_n_decimal_places(lyt_simulation_with_defect.get_system_energy(), 6) - + round_to_n_decimal_places(min_energy_defect, 6)) < std::numeric_limits::epsilon()) + { + lyt_simulation_with_defect.charge_distribution_to_index_general(); + charge_index_defect_layout = lyt_simulation_with_defect.get_charge_index_and_base().first; + } + } + + // defect changes the ground state, i.e., the charge index is changed compared to the charge + // distribution without placed defect. + if (charge_index_defect_layout != charge_index_layout) + { + auto distance = std::numeric_limits::max(); + layout.foreach_cell( + [this, &defect, &distance](const auto& cell) + { + if (sidb_nanometer_distance(layout, cell, defect) < distance) + { + distance = sidb_nanometer_distance(layout, cell, defect); + } + }); + + // the distance is larger than the current maximum one. + if (distance > avoidance_distance) + { + max_defect_position = defect; + avoidance_distance = distance; + } + } + }; + + // Apply the process_defect function to each defect using std::for_each + std::for_each(FICTION_EXECUTION_POLICY_PAR_UNSEQ defect_cells.cbegin(), defect_cells.cend(), process_defect); + + return {max_defect_position, avoidance_distance}; + } + + private: + /** + * SiDB cell-level layout to simulate. + */ + Lyt layout; + /** + * Parameters used for the simulation. + */ + maximum_defect_influence_distance_params params{}; + /** + * All allowed defect positions. + */ + std::vector defect_cells{}; + /** + * Collects all possible defect cell positions within a given layout while avoiding SiDB cells. + * + * This function calculates a bounding box around the provided layout, encompassing the area + * where defect cells can be placed. It then iterates through this bounding box, scanning from + * top to bottom and left to right, and identifies all valid positions for defect cells. A defect + * cell can only be placed in locations where there are no SiDB cells. + */ + void collect_all_defect_cells() noexcept + { + // bounding box around the given layout to have north-west and south-east cells. + bounding_box_2d bb{layout}; + + auto nw = bb.get_min(); // north-west cell + auto se = bb.get_max(); // south-east cell + + // shift nw and se cell by the additional scanning area to cover an area that is larger than the gate area. + nw.x = nw.x - params.additional_scanning_area.first; + nw.y = nw.y - params.additional_scanning_area.second; + + se.x = se.x + params.additional_scanning_area.first; + se.y = se.y + params.additional_scanning_area.second; + + // start to place the defect at the north-west cell + auto defect_cell = nw; + + // maximum number of placable defects in the given bounding box + const uint64_t max_defect_positions = + static_cast(std::abs(se.x - nw.x) + 1) * static_cast(std::abs(se.y - nw.y) + 1) * 2; + defect_cells.reserve(max_defect_positions); + + // collect all cells in the bounding box area (spanned by the nw and se) going from top to down from left to + // right. + while (defect_cell <= se) + { + // Defect can only be placed at free locations. + if (layout.get_cell_type(defect_cell) == sidb_technology::cell_type::EMPTY) + { + defect_cells.push_back(defect_cell); + } + if (defect_cell.x < se.x) + { + defect_cell.x += 1; + } + else if ((defect_cell.x == se.x) && defect_cell.z == 0) + { + defect_cell.z += 1; + defect_cell.x = nw.x; + } + else if ((defect_cell.x == se.x) && defect_cell.z == 1) + { + defect_cell.x = nw.x; + defect_cell.y += 1; + defect_cell.z = 0; + } + else + { + break; + } + } + } +}; + +} // namespace detail + +/** + * Calculates the maximum distance at which a given defect can influence the layout's ground state. + * + * This function simulates the influence of defects on a SiDB cell-level layout. It computes the + * maximum influence distance, defined as the minimum distance between any SiDB cell and the given defect, at which the + * defect can still affect the layout's ground state, potentially altering its behavior, such as gate functionality. + * + * @param lyt The SiDB cell-level layout for which the influence distance is being determined. + * @param params Parameters used to calculate the defect's maximum influence distance. + * @return Pair with the first element describing the position with maximum distance to the layout where a placed defect + * can still affect the ground state of the layout. The second entry describes the distance of the defect from the + * layout. + */ +template +std::pair +maximum_defect_influence_position_and_distance(const Lyt& lyt, + const maximum_defect_influence_distance_params& sim_params = {}) +{ + static_assert(is_cell_level_layout_v, "Lyt is not a cell-level layout"); + static_assert(has_sidb_technology_v, "Lyt is not an SiDB layout"); + static_assert(has_siqad_coord_v, "Lyt is not based on SiQAD coordinates"); + + detail::maximum_defect_influence_position_and_distance_impl p{lyt, sim_params}; + + return p.run(); +} + +} // namespace fiction + +#endif // FICTION_MAXIMUM_DEFECT_INFLUENCE_POSITION_AND_DISTANCE_HPP diff --git a/include/fiction/algorithms/simulation/sidb/occupation_probability_of_excited_states.hpp b/include/fiction/algorithms/simulation/sidb/occupation_probability_of_excited_states.hpp index 159f5e181..b42dd963a 100644 --- a/include/fiction/algorithms/simulation/sidb/occupation_probability_of_excited_states.hpp +++ b/include/fiction/algorithms/simulation/sidb/occupation_probability_of_excited_states.hpp @@ -39,12 +39,10 @@ namespace fiction return 0.0; } - auto min_energy = std::numeric_limits::infinity(); // unit: eV - // Determine the minimal energy. const auto [energy, state_type] = *std::min_element(energy_and_state_type.cbegin(), energy_and_state_type.cend(), [](const auto& a, const auto& b) { return a.first < b.first; }); - min_energy = energy; + const auto min_energy = energy; // The partition function is obtained by summing up all the Boltzmann factors. const double partition_function = @@ -85,10 +83,8 @@ namespace fiction return 0.0; } - auto min_energy = std::numeric_limits::infinity(); - const auto& [energy, degeneracy] = *(energy_distribution.begin()); - min_energy = energy; // unit: eV + const auto min_energy = energy; // unit: eV // The partition function is obtained by summing up all the Boltzmann factors. const double partition_function = diff --git a/include/fiction/algorithms/simulation/sidb/quickexact.hpp b/include/fiction/algorithms/simulation/sidb/quickexact.hpp new file mode 100644 index 000000000..5cd8c87a1 --- /dev/null +++ b/include/fiction/algorithms/simulation/sidb/quickexact.hpp @@ -0,0 +1,572 @@ +// +// Created by Jan Drewniok on 18.12.22. +// + +#ifndef FICTION_QUICKEXACT_HPP +#define FICTION_QUICKEXACT_HPP + +#include "fiction/algorithms/iter/gray_code_iterator.hpp" +#include "fiction/algorithms/simulation/sidb/energy_distribution.hpp" +#include "fiction/algorithms/simulation/sidb/enum_class_exhaustive_algorithm.hpp" +#include "fiction/algorithms/simulation/sidb/minimum_energy.hpp" +#include "fiction/algorithms/simulation/sidb/sidb_simulation_parameters.hpp" +#include "fiction/algorithms/simulation/sidb/sidb_simulation_result.hpp" +#include "fiction/technology/charge_distribution_surface.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace fiction +{ + +/** + * Modes to use for the `QuickExact` algorithm. + */ +enum class automatic_base_number_detection +{ + /** + * Simulation is conducted with the required base number (i.e, if positively charged SiDBs can occur, three state + * simulation is conducted). + */ + ON, + /** + * The base number from the physical parameter is used for the simulation. + */ + OFF +}; +/** + * This struct stores the parameters for the `QuickExact` algorithm. + */ +template +struct quickexact_params +{ + /** + * All parameters for physical SiDB simulations. + */ + sidb_simulation_parameters physical_parameters{}; + /** + * If ON, `QuickExact` checks before which base number is required for the simulation, i.e., whether 3-state is + * necessary or 2-state simulation is sufficient. + */ + automatic_base_number_detection base_number_detection = automatic_base_number_detection::ON; + /** + * Local external electrostatic potentials (e.g locally applied electrodes). + */ + std::unordered_map local_external_potential = {}; + /** + * Global external electrostatic potential. Value is applied on each cell in the layout. + */ + double global_potential = 0; +}; + +namespace detail +{ + +template +class quickexact_impl +{ + public: + quickexact_impl(Lyt& lyt, const quickexact_params& parameter) : + layout{lyt}, + charge_lyt{lyt, parameter.physical_parameters, sidb_charge_state::NEGATIVE}, + params{parameter} + {} + + sidb_simulation_result run() noexcept + { + result.algorithm_name = "QuickExact"; + result.physical_parameters = params.physical_parameters; + + mockturtle::stopwatch<>::duration time_counter{}; + { + const mockturtle::stopwatch stop{time_counter}; + + initialize_charge_layout(); + + // Determine if three state simulation (i.e., positively charged SiDBs can occur) is required. + const bool three_state_simulation_required = + (params.base_number_detection == automatic_base_number_detection::ON && + charge_lyt.is_three_state_simulation_required()) || + (params.base_number_detection == automatic_base_number_detection::OFF && + params.physical_parameters.base == 3); + + // If layout has at least two SiDBs, all SiDBs that have to be negatively charged are erased from the + // layout. + if (number_of_sidbs > 1) + { + generate_layout_without_negative_sidbs(); + + // If the layout consists of SiDBs that do not need to be negatively charged. + if (!all_sidbs_in_lyt_without_negative_preassigned_ones.empty()) + { + // The first cell from all_sidbs_in_lyt_without_negative_preassigned_ones is chosen as the + // dependent-cell to initialize the layout (pre-assigned negatively charged SiDBs were erased with + // generate_layout_without_negative_sidbs). All SiDBs are set to neutrally charged. + charge_distribution_surface charge_lyt_with_assigned_dependent_cell{ + layout, params.physical_parameters, sidb_charge_state::NEUTRAL, + all_sidbs_in_lyt_without_negative_preassigned_ones[0]}; + + charge_lyt_with_assigned_dependent_cell.assign_local_external_potential( + params.local_external_potential); + charge_lyt_with_assigned_dependent_cell.assign_global_external_potential(params.global_potential); + + if constexpr (has_get_sidb_defect_v) + { + for (const auto& [cell, defect] : real_placed_defects) + { + charge_lyt_with_assigned_dependent_cell.add_sidb_defect_to_potential_landscape(cell, + defect); + } + } + + // IMPORTANT: The pre-assigned negatively charged SiDBs (they have to be negatively charged to + // fulfill the population stability) are considered as negatively charged defects in the layout. + // Hence, there are no "real" defects assigned, but in order to set some SiDBs with a fixed negative + // charge, this way of implementation is chosen. + for (const auto& cell : preassigned_negative_sidbs) + { + charge_lyt_with_assigned_dependent_cell.add_sidb_defect_to_potential_landscape( + cell, sidb_defect{sidb_defect_type::UNKNOWN, -1, + charge_lyt_with_assigned_dependent_cell.get_phys_params().epsilon_r, + charge_lyt_with_assigned_dependent_cell.get_phys_params().lambda_tf}); + } + + // Update all local potentials, system energy and physically validity. The Flag is set to "Variable" + // to allow dependent cell to change its charge state based on the N-1 SiDBs to fulfill the local + // population stability in its position. + charge_lyt_with_assigned_dependent_cell.update_after_charge_change(dependent_cell_mode::VARIABLE); + + // If no positively charged SiDB can occur in the layout. + if (!three_state_simulation_required) + { + result.additional_simulation_parameters.emplace_back("base_number", uint64_t{2}); + two_state_simulation(charge_lyt_with_assigned_dependent_cell); + } + // If positively charged SiDBs can occur in the layout, 3-state simulation is conducted. + else + { + result.additional_simulation_parameters.emplace_back("base_number", uint64_t{3}); + three_state_simulation(charge_lyt_with_assigned_dependent_cell); + } + } + + // If the layout consists of only pre-assigned negatively-charged SiDBs + // (i.e., only SiDBs that are far away from each other). + else if (all_sidbs_in_lyt_without_negative_preassigned_ones.empty()) + { + charge_distribution_surface charge_lyt_copy{charge_lyt}; + if constexpr (has_get_sidb_defect_v) + { + for (const auto& [cell, defect] : real_placed_defects) + { + charge_lyt_copy.assign_sidb_defect(cell, defect); + } + } + result.charge_distributions.push_back(charge_lyt_copy); + } + } + // If there is only one SiDB in the layout, this single SiDB can be neutrally or even positively charged due + // to external potentials or defects. + else if (number_of_sidbs == 1) + { + if (three_state_simulation_required) + { + charge_lyt.assign_base_number(3); + } + else + { + charge_lyt.assign_base_number(2); + } + + // A check is performed to see if the charge index is still below the maximum charge index. If not, the + // charge index is increased and the corresponding charge distribution is checked for physical validity. + while (charge_lyt.get_charge_index_and_base().first < charge_lyt.get_max_charge_index()) + { + if (charge_lyt.is_physically_valid()) + { + charge_distribution_surface charge_lyt_copy{charge_lyt}; + if constexpr (has_get_sidb_defect_v) + { + for (const auto& [cell, defect] : real_placed_defects) + { + charge_lyt_copy.assign_sidb_defect(cell, defect); + } + } + result.charge_distributions.push_back(charge_lyt_copy); + } + + charge_lyt.increase_charge_index_by_one( + dependent_cell_mode::VARIABLE); // "Variable" allows that the charge state of the dependent + // cell is automatically changed based on the new charge + // distribution. + } + + if (charge_lyt.is_physically_valid()) + { + charge_distribution_surface charge_lyt_copy{charge_lyt}; + if constexpr (has_get_sidb_defect_v) + { + for (const auto& [cell, defect] : real_placed_defects) + { + charge_lyt_copy.assign_sidb_defect(cell, defect); + } + } + result.charge_distributions.push_back(charge_lyt_copy); + } + } + + for (const auto& cell : preassigned_negative_sidbs) + { + layout.assign_cell_type(cell, Lyt::cell_type::NORMAL); + } + } + + result.simulation_runtime = time_counter; + + return result; + } + + private: + /** + * Layout to simulate. + */ + Lyt layout; + /** + * Charge distribution surface. + */ + charge_distribution_surface charge_lyt{}; + /** + * Parameters used for the simulation. + */ + quickexact_params params{}; + /** + * Indices of all SiDBs that are pre-assigned to be negatively charged in a physically valid layout. + */ + std::vector preassigned_negative_sidb_indices{}; + /** + * All SiDBs that are pre-assigned to be negatively charged in a physically valid layout. + */ + std::vector preassigned_negative_sidbs{}; + /** + * All SiDBs of the layout but without the negatively-charged SiDBs. + */ + std::vector all_sidbs_in_lyt_without_negative_preassigned_ones{}; + /** + * Collection of defects that are placed in addition to the SiDBs. + */ + std::unordered_map real_placed_defects{}; + /** + * Number of SiDBs of the input layout. + */ + uint64_t number_of_sidbs{}; + /** + * Simulation results. + */ + sidb_simulation_result result{}; + + /** + * This function conducts 2-state physical simulation (negative, neutral). + * + * @param charge_layout Initialized charge layout. + */ + void two_state_simulation(charge_distribution_surface& charge_layout) noexcept + { + charge_layout.assign_base_number(2); + uint64_t previous_charge_index = 0; + + gray_code_iterator gci{0}; + + for (gci = 0; gci <= charge_layout.get_max_charge_index(); ++gci) + { + charge_layout.assign_charge_index_by_gray_code(*gci, previous_charge_index, dependent_cell_mode::VARIABLE, + energy_calculation::KEEP_OLD_ENERGY_VALUE, + charge_distribution_history::CONSIDER); + + previous_charge_index = *gci; + + if (charge_layout.is_physically_valid()) + { + charge_distribution_surface charge_lyt_copy{charge_layout}; + charge_lyt_copy.recompute_system_energy(); + + // The pre-assigned negatively-charged SiDBs are added to the final layout. + for (const auto& cell : preassigned_negative_sidbs) + { + charge_lyt_copy.add_sidb(cell, sidb_charge_state::NEGATIVE); + } + + if constexpr (has_get_sidb_defect_v) + { + for (const auto& [cell, defect] : real_placed_defects) + { + charge_lyt_copy.assign_sidb_defect(cell, defect); + } + } + result.charge_distributions.push_back(charge_lyt_copy); + } + } + + // The cells of the pre-assigned negatively-charged SiDBs are added to the cell level layout. + for (const auto& cell : preassigned_negative_sidbs) + { + layout.assign_cell_type(cell, Lyt::cell_type::NORMAL); + } + } + /** + * This function conducts 3-state physical simulation (negative, neutral, positive). + * + * @param charge_layout Initialized charge layout. + */ + void three_state_simulation(charge_distribution_surface& charge_layout) noexcept + { + charge_layout.assign_all_charge_states(sidb_charge_state::NEGATIVE); + charge_layout.update_after_charge_change(); + // Not executed to detect if 3-state simulation is required, but to detect the SiDBs that could be positively + // charged (important to speed up the simulation). + charge_layout.is_three_state_simulation_required(); + charge_layout.update_after_charge_change(dependent_cell_mode::VARIABLE); + + while (charge_layout.get_charge_index_and_base().first < charge_layout.get_max_charge_index()) + { + while (charge_layout.get_charge_index_of_sub_layout() < charge_layout.get_max_charge_index_sub_layout()) + { + if (charge_layout.is_physically_valid()) + { + charge_distribution_surface charge_lyt_copy{charge_layout}; + charge_lyt_copy.recompute_system_energy(); + + // The pre-assigned negatively-charged SiDBs are added to the final layout. + for (const auto& cell : preassigned_negative_sidbs) + { + charge_lyt_copy.add_sidb(cell, sidb_charge_state::NEGATIVE); + } + + if constexpr (has_get_sidb_defect_v) + { + for (const auto& [cell, defect] : real_placed_defects) + { + charge_lyt_copy.assign_sidb_defect(cell, defect); + } + } + + result.charge_distributions.push_back(charge_lyt_copy); + } + + charge_layout.increase_charge_index_of_sub_layout_by_one( + dependent_cell_mode::VARIABLE, energy_calculation::KEEP_OLD_ENERGY_VALUE, + charge_distribution_history::CONSIDER, + exhaustive_algorithm::QUICKEXACT); // "false" allows that the charge state of the dependent cell is + // automatically changed based on the new charge distribution. + } + + if (charge_layout.is_physically_valid()) + { + charge_distribution_surface charge_lyt_copy{charge_layout}; + charge_lyt_copy.recompute_system_energy(); + + for (const auto& cell : preassigned_negative_sidbs) + { + charge_lyt_copy.add_sidb(cell, sidb_charge_state::NEGATIVE); + } + + if constexpr (has_get_sidb_defect_v) + { + for (const auto& [cell, defect] : real_placed_defects) + { + charge_lyt_copy.assign_sidb_defect(cell, defect); + } + } + + result.charge_distributions.push_back(charge_lyt_copy); + } + + if (charge_layout.get_max_charge_index_sub_layout() != 0) + { + charge_layout.reset_charge_index_sub_layout(); + } + + charge_layout.increase_charge_index_by_one( + dependent_cell_mode::VARIABLE, energy_calculation::KEEP_OLD_ENERGY_VALUE, + charge_distribution_history::CONSIDER, + exhaustive_algorithm::QUICKEXACT); // "false" allows that the charge state of the dependent cell is + // automatically changed based on the new charge distribution. + } + + // charge configurations of the sublayout are iterated + while (charge_layout.get_charge_index_of_sub_layout() < charge_layout.get_max_charge_index_sub_layout()) + { + if (charge_layout.is_physically_valid()) + { + charge_distribution_surface charge_lyt_copy{charge_layout}; + charge_lyt_copy.recompute_system_energy(); + + // The pre-assigned negatively-charged SiDBs are added to the final layout. + for (const auto& cell : preassigned_negative_sidbs) + { + charge_lyt_copy.add_sidb(cell, sidb_charge_state::NEGATIVE); + } + + if constexpr (has_get_sidb_defect_v) + { + for (const auto& [cell, defect] : real_placed_defects) + { + charge_lyt_copy.assign_sidb_defect(cell, defect); + } + } + + result.charge_distributions.push_back(charge_lyt_copy); + } + + charge_layout.increase_charge_index_of_sub_layout_by_one( + dependent_cell_mode::VARIABLE, energy_calculation::KEEP_OLD_ENERGY_VALUE, + charge_distribution_history::CONSIDER, exhaustive_algorithm::QUICKEXACT); + } + + if (charge_layout.is_physically_valid()) + { + charge_distribution_surface charge_lyt_copy{charge_layout}; + + for (const auto& cell : preassigned_negative_sidbs) + { + charge_lyt_copy.add_sidb(cell, sidb_charge_state::NEGATIVE); + } + + if constexpr (has_get_sidb_defect_v) + { + for (const auto& [cell, defect] : real_placed_defects) + { + charge_lyt_copy.assign_sidb_defect(cell, defect); + } + } + + result.charge_distributions.push_back(charge_lyt_copy); + } + + for (const auto& cell : preassigned_negative_sidbs) + { + layout.assign_cell_type(cell, Lyt::cell_type::NORMAL); + } + } + /** + * This function is responsible for preparing the charge layout and relevant data structures for the simulation. + * + * This function initializes the charge layout within the context of the current simulation. It performs the + * following tasks: + * + * - If the provided layout type `Lyt` supports a `foreach_sidb_defect` method, it iterates through each + * defect in the layout. + * - If a defect is found, it adds the SiDB defect to the potential landscape. + * - It assigns the local external potential from the `params.local_external_potential` configuration to the charge + * layout. + * - It assigns the global external potential from `params.global_potential` to the charge layout. + * + */ + void initialize_charge_layout() noexcept + { + if constexpr (has_foreach_sidb_defect_v) + { + layout.foreach_sidb_defect( + [this](const auto& cd) + { + const auto& [cell, defect] = cd; + + if (defect.type != sidb_defect_type::NONE) + { + charge_lyt.add_sidb_defect_to_potential_landscape(cell, layout.get_sidb_defect(cell)); + } + }); + } + + charge_lyt.assign_local_external_potential(params.local_external_potential); + charge_lyt.assign_global_external_potential(params.global_potential, dependent_cell_mode::VARIABLE); + + preassigned_negative_sidb_indices = charge_lyt.negative_sidb_detection(); + preassigned_negative_sidbs.reserve(preassigned_negative_sidb_indices.size()); + + all_sidbs_in_lyt_without_negative_preassigned_ones = charge_lyt.get_sidb_order(); + real_placed_defects = charge_lyt.get_defects(); + // store the number of SiDBs, since the number of active cells changes during simulation. + number_of_sidbs = charge_lyt.num_cells(); + } + /** + * This function is used to generate a layout without the SiDBs that are pre-assigned to be negatively charged in a + * physically-valid layout. + */ + void generate_layout_without_negative_sidbs() noexcept + { + for (const auto& index : preassigned_negative_sidb_indices) + { + const auto cell = charge_lyt.index_to_cell(static_cast(index)); + preassigned_negative_sidbs.push_back(cell); + layout.assign_cell_type(cell, Lyt::cell_type::EMPTY); + } + + // All pre-assigned negatively-charged SiDBs are erased from the + // all_sidbs_in_lyt_without_negative_preassigned_ones vector. + all_sidbs_in_lyt_without_negative_preassigned_ones.erase( + std::remove_if(all_sidbs_in_lyt_without_negative_preassigned_ones.begin(), + all_sidbs_in_lyt_without_negative_preassigned_ones.end(), + [this](const auto& n) + { + return std::find(preassigned_negative_sidbs.cbegin(), preassigned_negative_sidbs.cend(), + n) != preassigned_negative_sidbs.cend(); + }), + all_sidbs_in_lyt_without_negative_preassigned_ones.cend()); + } +}; + +} // namespace detail + +/** + * `QuickExact` is a quick and exact physical simulation algorithm designed specifically for SiDB layouts. It is + * proposed in \"The Need for Speed: Efficient Exact Simulation of Silicon Dangling Bond Logic\" by J. Drewniok, M. + * Walter, and R. Wille (https://arxiv.org/abs/2308.04487). It determines all physically valid charge configurations of + * a given layout, providing a significant performance advantage of more than three orders of magnitude over `ExGS` + * (exhaustive_ground_state_simulation.hpp). + * + * The performance improvement of `QuickExact` can be attributed to the incorporation of three key ideas: + * + * 1. Advanced Negative SiDB Detection: `QuickExact` efficiently identifies SiDBs that require negative charges + * in a physically valid charge distribution. By pre-assigned them in advance, the search space is pruned + * by a factor of \f$2^k\f$, where k is the number of found SiDBs. + * + * 2. Dependent SiDB Selection: The algorithm selects a dependent SiDB, whose charge state is always derived + * from its n-1 neighbors. This dependency simplifies the computation process and contributes to the overall + * efficiency of `QuickExact`. + * + * 3. Gray Code Representation: `QuickExact` employs Gray code to represent and traverse through all charge + * configurations. By using Gray code, only one charge state changes at a time, making the computation + * of the local electrostatic potential easier. + * + * Additionally, `QuickExact` also considers global and local electrostatic potentials, as well as existing defects. + * This holistic approach ensures an accurate representation of the physical behavior of the SiDB layout. + * + * In summary, `QuickExact` combines advanced SiDB charge detection, dependent SiDB selection, and the use of Gray code + * to achieve outstanding performance and enable efficient simulations of SiDB layouts, even in scenarios where + * positively-charged SiDBs occur due to small spacing. + * + * @tparam Lyt SiDB cell-level layout type. + * @param lyt Layout to simulate. + * @param params Parameter required for the simulation. + * @return Simulation result. + */ +template +[[nodiscard]] sidb_simulation_result quickexact(Lyt& lyt, const quickexact_params& params = {}) noexcept +{ + static_assert(is_cell_level_layout_v, "Lyt is not a cell-level layout"); + static_assert(has_sidb_technology_v, "Lyt is not an SiDB layout"); + static_assert(has_siqad_coord_v, "Lyt is not based on SiQAD coordinates"); + + detail::quickexact_impl p{lyt, params}; + + return p.run(); +} + +} // namespace fiction + +#endif // FICTION_QUICKEXACT_HPP diff --git a/include/fiction/algorithms/simulation/sidb/quicksim.hpp b/include/fiction/algorithms/simulation/sidb/quicksim.hpp index 29f8ae57a..0ae9ace0c 100644 --- a/include/fiction/algorithms/simulation/sidb/quicksim.hpp +++ b/include/fiction/algorithms/simulation/sidb/quicksim.hpp @@ -6,6 +6,7 @@ #define FICTION_QUICKSIM_HPP #include "fiction/algorithms/simulation/sidb/energy_distribution.hpp" +#include "fiction/algorithms/simulation/sidb/enum_class_exhaustive_algorithm.hpp" #include "fiction/algorithms/simulation/sidb/minimum_energy.hpp" #include "fiction/algorithms/simulation/sidb/sidb_simulation_result.hpp" #include "fiction/technology/charge_distribution_surface.hpp" @@ -84,10 +85,10 @@ sidb_simulation_result quicksim(const Lyt& lyt, const quicksim_params& ps = charge_distribution_surface charge_lyt{lyt}; // set the given physical parameters - charge_lyt.set_physical_parameters(ps.phys_params); - - charge_lyt.set_all_charge_states(sidb_charge_state::NEGATIVE); - charge_lyt.update_after_charge_change(); + charge_lyt.assign_physical_parameters(ps.phys_params); + charge_lyt.assign_base_number(2); + charge_lyt.assign_all_charge_states(sidb_charge_state::NEGATIVE); + charge_lyt.update_after_charge_change(dependent_cell_mode::VARIABLE); const auto negative_sidb_indices = charge_lyt.negative_sidb_detection(); // Check that the layout with all SiDBs negatively charged is physically valid. @@ -97,7 +98,7 @@ sidb_simulation_result quicksim(const Lyt& lyt, const quicksim_params& ps = } // Check that the layout with all SiDBs neutrally charged is physically valid. - charge_lyt.set_all_charge_states(sidb_charge_state::NEUTRAL); + charge_lyt.assign_all_charge_states(sidb_charge_state::NEUTRAL); charge_lyt.update_after_charge_change(); if (!negative_sidb_indices.empty()) @@ -120,7 +121,7 @@ sidb_simulation_result quicksim(const Lyt& lyt, const quicksim_params& ps = st.charge_distributions.push_back(charge_distribution_surface{charge_lyt}); } - charge_lyt.set_all_charge_states(sidb_charge_state::NEUTRAL); + charge_lyt.assign_all_charge_states(sidb_charge_state::NEUTRAL); charge_lyt.update_after_charge_change(); // If the number of threads is initially set to zero, the simulation is run with one thread. @@ -158,7 +159,7 @@ sidb_simulation_result quicksim(const Lyt& lyt, const quicksim_params& ps = std::vector index_start{i}; - charge_lyt_copy.set_all_charge_states(sidb_charge_state::NEUTRAL); + charge_lyt_copy.assign_all_charge_states(sidb_charge_state::NEUTRAL); for (const auto& index : negative_sidb_indices) { diff --git a/include/fiction/algorithms/simulation/sidb/time_to_solution.hpp b/include/fiction/algorithms/simulation/sidb/time_to_solution.hpp index 08da1f31a..7552ce475 100644 --- a/include/fiction/algorithms/simulation/sidb/time_to_solution.hpp +++ b/include/fiction/algorithms/simulation/sidb/time_to_solution.hpp @@ -5,10 +5,13 @@ #ifndef FICTION_TIME_TO_SOLUTION_HPP #define FICTION_TIME_TO_SOLUTION_HPP +#include "fiction/algorithms/simulation/sidb/enum_class_exhaustive_algorithm.hpp" #include "fiction/algorithms/simulation/sidb/exhaustive_ground_state_simulation.hpp" #include "fiction/algorithms/simulation/sidb/is_ground_state.hpp" #include "fiction/algorithms/simulation/sidb/minimum_energy.hpp" +#include "fiction/algorithms/simulation/sidb/quickexact.hpp" #include "fiction/algorithms/simulation/sidb/quicksim.hpp" +#include "fiction/algorithms/simulation/sidb/sidb_simulation_result.hpp" #include "fiction/technology/charge_distribution_surface.hpp" #include "fiction/traits.hpp" @@ -24,9 +27,27 @@ namespace fiction { +struct time_to_solution_params +{ + /** + * Exhaustive simulation algorithm used to simulate the ground state as reference. + */ + exhaustive_algorithm engine = exhaustive_algorithm::QUICKEXACT; + /** + * Number of iterations of the heuristic algorithm used to determine the simulation accuracy (`repetitions = 100` + * means that accuracy is precise to 1%). + */ + uint64_t repetitions = 100; + /** + * Confidence level. + */ + double confidence_level = 0.997; +}; + /** * 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), the single runtime of the exact simulator used, and the number of valid charge + * configurations found by the exact algorithm. * */ struct time_to_solution_stats @@ -43,12 +64,14 @@ struct time_to_solution_stats * Average single simulation runtime in seconds. */ double mean_single_runtime{}; - /** * Single simulation runtime of the exhaustive ground state searcher in seconds. */ double single_runtime_exhaustive{}; - + /** + * Exhaustive simulation algorithm used to simulate the ground state as reference. + */ + std::string algorithm; /** * Print the results to the given output stream. * @@ -56,8 +79,8 @@ struct time_to_solution_stats */ void report(std::ostream& out = std::cout) { - out << fmt::format("[i] time_to_solution: {} | acc: {} | t_(s): {} | t_exhaustive(s): {}\n", time_to_solution, - acc, mean_single_runtime, single_runtime_exhaustive); + out << fmt::format("time_to_solution: {} \n acc: {} \n t_(s): {} \n t_exhaustive(s): {} \n exact alg.: {}\n", + time_to_solution, acc, mean_single_runtime, single_runtime_exhaustive, algorithm); } }; /** @@ -65,30 +88,40 @@ struct time_to_solution_stats * * @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 quicksim_params Parameters required for the QuickSim algorithm. * @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` 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. + * @param tts_params Parameters used for the time-to-solution calculation. */ template -void sim_acc_tts(const Lyt& lyt, const quicksim_params& quicksim_params, time_to_solution_stats* ps = nullptr, - const uint64_t& repetitions = 100, const double confidence_level = 0.997) noexcept +void sim_acc_tts(Lyt& lyt, const quicksim_params& quicksim_params, const time_to_solution_params& tts_params = {}, + time_to_solution_stats* ps = nullptr) noexcept { static_assert(is_cell_level_layout_v, "Lyt is not a cell-level layout"); static_assert(has_sidb_technology_v, "Lyt is not an SiDB layout"); static_assert(has_siqad_coord_v, "Lyt is not based on SiQAD coordinates"); - const auto simulation_results_exgs = exhaustive_ground_state_simulation(lyt, quicksim_params.phys_params); - time_to_solution_stats st{}; - st.single_runtime_exhaustive = mockturtle::to_seconds(simulation_results_exgs.simulation_runtime); + + sidb_simulation_result simulation_result{}; + if (tts_params.engine == exhaustive_algorithm::EXGS) + { + st.algorithm = "ExGS"; + simulation_result = exhaustive_ground_state_simulation(lyt, quicksim_params.phys_params); + } + else + { + const quickexact_params params{quicksim_params.phys_params}; + st.algorithm = "QuickExact"; + simulation_result = quickexact(lyt, params); + } + + st.single_runtime_exhaustive = mockturtle::to_seconds(simulation_result.simulation_runtime); std::size_t gs_count = 0; std::vector time{}; - time.reserve(repetitions); + time.reserve(tts_params.repetitions); - for (auto i = 0u; i < repetitions; ++i) + for (auto i = 0u; i < tts_params.repetitions; ++i) { sidb_simulation_result stats_quick{}; @@ -102,16 +135,17 @@ void sim_acc_tts(const Lyt& lyt, const quicksim_params& quicksim_params, time_to time.push_back(diff_first); - if (is_ground_state(simulation_results_quicksim, simulation_results_exgs)) + if (is_ground_state(simulation_results_quicksim, simulation_result)) { gs_count += 1; } } - const auto single_runtime = std::accumulate(time.begin(), time.end(), 0.0) / static_cast(repetitions); - const auto acc = static_cast(gs_count) / static_cast(repetitions); + const auto single_runtime = + std::accumulate(time.cbegin(), time.cend(), 0.0) / static_cast(tts_params.repetitions); + const auto acc = static_cast(gs_count) / static_cast(tts_params.repetitions); - double tts = single_runtime; + double tts = 0.0; if (acc == 1) { @@ -123,7 +157,7 @@ void sim_acc_tts(const Lyt& lyt, const quicksim_params& quicksim_params, time_to } else { - tts = (single_runtime * std::log(1.0 - confidence_level) / std::log(1.0 - acc)); + tts = (single_runtime * std::log(1.0 - tts_params.confidence_level) / std::log(1.0 - acc)); } st.time_to_solution = tts; diff --git a/include/fiction/io/write_location_and_ground_state.hpp b/include/fiction/io/write_location_and_ground_state.hpp index 90736fc02..ed6a1a2f0 100644 --- a/include/fiction/io/write_location_and_ground_state.hpp +++ b/include/fiction/io/write_location_and_ground_state.hpp @@ -63,7 +63,7 @@ class write_location_and_ground_state_impl } os << '\n'; - auto sidbs = ground_state_layouts.front().get_all_sidb_cells(); + auto sidbs = ground_state_layouts.front().get_sidb_order(); const auto physical_parameter = ground_state_layouts.front().get_phys_params(); std::sort(sidbs.begin(), sidbs.end()); diff --git a/include/fiction/technology/charge_distribution_surface.hpp b/include/fiction/technology/charge_distribution_surface.hpp index 0e84356e1..6dc753f71 100644 --- a/include/fiction/technology/charge_distribution_surface.hpp +++ b/include/fiction/technology/charge_distribution_surface.hpp @@ -6,15 +6,18 @@ #define FICTION_CHARGE_DISTRIBUTION_SURFACE_HPP #include "fiction/algorithms/path_finding/distance.hpp" +#include "fiction/algorithms/simulation/sidb/enum_class_exhaustive_algorithm.hpp" #include "fiction/algorithms/simulation/sidb/sidb_simulation_parameters.hpp" #include "fiction/layouts/cell_level_layout.hpp" #include "fiction/technology/physical_constants.hpp" #include "fiction/technology/sidb_charge_state.hpp" +#include "fiction/technology/sidb_defects.hpp" #include "fiction/technology/sidb_nm_position.hpp" #include "fiction/traits.hpp" #include "fiction/types.hpp" #include +#include #include #include #include @@ -29,11 +32,58 @@ namespace fiction { +/** + * An enumeration of modes for the dependent cell. + */ +enum class dependent_cell_mode +{ + /** + * The charge state of the dependent cell is not changed based on the local electrostatic potential at its position. + */ + FIXED, + /** + * The charge state of the dependent cell is changed based on the local electrostatic potential at its position. + */ + VARIABLE +}; + +/** + * An enumeration of modes for calculation of the electrostatic potential energy of a given charge distribution. + */ +enum class energy_calculation +{ + /** + * The electrostatic potential energy of a given charge distribution is not updated after it is changed. + */ + KEEP_OLD_ENERGY_VALUE, + /** + * The electrostatic potential energy of a given charge distribution is updated after it is changed. + */ + UPDATE_ENERGY +}; + +/** + * An enumeration of modes to decide if the previous charge distribution is used to simply the computation of the + * properties of a new charge distribution. + */ +enum class charge_distribution_history +{ + /** + * The previous charge distribution is used. + */ + CONSIDER, + /** + * The previous charge distribution is not used. Hence, the local electrostatic potential of the given charge + * distribution is calculated from scratch. + */ + NEGLECT +}; + /** * A layout type to layer on top of any SiDB cell-level layout. It implements an interface to store and access * SiDBs' charge states. * - * @tparam Lyt Cell-level layout based in SiQAD-coordinates. + * @tparam Lyt Cell-level layout based in SiQAD coordinates. * @tparam has_sidb_charge_distribution Automatically determines whether a charge distribution interface is already * present. */ @@ -72,8 +122,20 @@ class charge_distribution_surface : public Lyt using local_potential = std::vector; public: - explicit charge_distribution_storage(const sidb_simulation_parameters& params = sidb_simulation_parameters{}) : - phys_params{params} {}; + /** + * Standard constructor for the charge_distribution_storage. + * + * @param params Physical parameters used for the simulation (µ_minus, base number, ...). + * @param external_potential Externally applied local electrostatic potential. + * @param variable_cell SiDB which charge state is variable (called dependent-cell). + */ + explicit charge_distribution_storage( + const sidb_simulation_parameters& params = sidb_simulation_parameters{}, + const std::unordered_map& external_potential = {}, + const typename Lyt::cell& variable_cell = {}) : + phys_params{params}, + local_external_pot{external_potential}, + dependent_cell{variable_cell} {}; /** * Stores all physical parameters used for the simulation. */ @@ -82,6 +144,10 @@ class charge_distribution_surface : public Lyt * All cells that are occupied by an SiDB are stored in order. */ std::vector sidb_order{}; + /** + * All cells that cannot be positively charged in a physically valid layout. + */ + std::vector sidb_order_without_three_state_cells{}; /** * The SiDBs' charge states are stored. Corresponding cells are stored in `sidb_order`. */ @@ -94,29 +160,79 @@ class charge_distribution_surface : public Lyt * Electrostatic potential between SiDBs are stored as matrix (here, still charge-independent, unit: V). */ potential_matrix pot_mat{}; + /** + * Electrostatic potential at each SiDB position which is generated by defects (unit: eV). + */ + std::unordered_map defect_local_pot{}; + /** + * External electrostatic potential in V at each SiDB position (can be used when different potentials are + * applied to different SiDBs). + */ + std::unordered_map local_external_pot{}; /** * Electrostatic potential at each SiDB position. Has to be updated when charge distribution is changed (unit: * V). */ - local_potential loc_pot{}; + local_potential local_pot{}; /** * Stores the electrostatic energy of a given charge distribution (unit: eV). */ double system_energy{0.0}; /** - * Labels if given charge distribution is physically valid (see https://ieeexplore.ieee.org/document/8963859). + * Label if given charge distribution is physically valid (see https://ieeexplore.ieee.org/document/8963859). */ bool validity = false; /** * Each charge distribution is assigned a unique index (first entry of pair), second one stores the base number * (2- or 3-state simulation). */ - charge_index_base charge_index{}; + charge_index_base charge_index_and_base{}; + /** + * Charge index of the sublayout (collection of SiDBs that could be positively charged for a specific charge + * configuration of the layout). + */ + uint64_t charge_index_sublayout{}; /** * Depending on the number of SiDBs and the base number, a maximal number of possible charge distributions * exists. */ uint64_t max_charge_index{}; + /** + * Depending on the number of SiDBs in the SiDBs, a maximal number of possible charge distributions + * exists. + */ + uint64_t max_charge_index_sulayout{}; + /** + * This pair stores the cell index and its previously charge state (important when all possible charge + * distributions are enumerated and checked for physical validity). + */ + std::pair cell_history_gray_code{}; + /** + * This vector stores the cells and its previously charge states of the charge distribution before the charge + * index was changed. + */ + std::vector> cell_history{}; + /** + * This unordered map stores the cells and the placed defect. + */ + std::unordered_map defects{}; + /** + * Dependent cell is the cell which charge state is determined by all other SiDBs in the layout. + */ + typename Lyt::cell dependent_cell{}; + /** + * Charge index of the dependent cell in the layout. + */ + uint64_t dependent_cell_index{}; + /** + * This vector collects all cells that could potentially be positively charged based on the maximum possible + * local potential. + */ + std::vector three_state_cells{}; + /** + * True indicates that the dependent SiDB is in the sublayout. + */ + bool dependent_cell_in_sub_layout{}; }; using storage = std::shared_ptr; @@ -141,15 +257,18 @@ class charge_distribution_surface : public Lyt /** * Standard constructor for existing layouts. * - * @param lyt The layout to be used as base. + * @param lyt Cell-level layout. * @param params Physical parameters used for the simulation (µ_minus, base number, ...). * @param cs The charge state used for the initialization of all SiDBs, default is a negative charge. + * @param variable_cells SiDB which charge state is variable (called dependent-cell). + * @param external_potential Externally applied local electrostatic potential. */ - explicit charge_distribution_surface(const Lyt& lyt, - const sidb_simulation_parameters& params = sidb_simulation_parameters{}, - const sidb_charge_state& cs = sidb_charge_state::NEGATIVE) : + explicit charge_distribution_surface( + const Lyt& lyt, const sidb_simulation_parameters& params = sidb_simulation_parameters{}, + const sidb_charge_state& cs = sidb_charge_state::NEGATIVE, const typename Lyt::cell& variable_cells = {}, + const std::unordered_map& external_potential = {}) : Lyt(lyt), - strg{std::make_shared(params)} + strg{std::make_shared(params, external_potential, variable_cells)} { static_assert(has_siqad_coord_v, "Lyt is not based on SiQAD coordinates"); static_assert(is_cell_level_layout_v, "Lyt is not a cell-level layout"); @@ -181,18 +300,9 @@ class charge_distribution_surface : public Lyt return *this; } /** - * Returns all SiDB charges of the placed SiDBs as a vector. + * This function returns the locations of all SiDBs in nm of the form `(x,y)`. * - * @return Vector of SiDB charges. - */ - [[nodiscard]] std::vector get_all_sidb_charges() const noexcept - { - return strg->cell_charge; - } - /** - * Returns the locations of all SiDBs in nm of the form `(x,y)`. - * - * @return Vector of SiDB nanometer positions. + * @return Vector of SiDB nanometer positions (unit: nm). */ [[nodiscard]] std::vector> get_all_sidb_locations_in_nm() const noexcept { @@ -208,28 +318,19 @@ class charge_distribution_surface : public Lyt return positions; } /** - * Returns all SiDB cells. - * - * @return Vector of SiDB cells. - */ - [[nodiscard]] std::vector get_all_sidb_cells() const noexcept - { - return strg->sidb_order; - } - /** - * Set the physical parameters for the simulation. + * This function assigns the physical parameters for the simulation. * - * @param params Physical parameters to be set. + * @param params Physical parameters to be assigned. */ - void set_physical_parameters(const sidb_simulation_parameters& params) noexcept + void assign_physical_parameters(const sidb_simulation_parameters& params) noexcept { if ((strg->phys_params.base == params.base) && (strg->phys_params.lat_b == params.lat_b) && (strg->phys_params.lat_c == params.lat_c) && (strg->phys_params.epsilon_r == params.epsilon_r) && (strg->phys_params.lambda_tf == params.lambda_tf)) { - strg->phys_params = params; - strg->charge_index.second = params.base; - strg->max_charge_index = static_cast(std::pow(strg->phys_params.base, this->num_cells())) - 1; + strg->phys_params = params; + strg->charge_index_and_base.second = params.base; + strg->max_charge_index = static_cast(std::pow(strg->phys_params.base, this->num_cells())) - 1; this->update_local_potential(); this->recompute_system_energy(); this->validity_check(); @@ -239,42 +340,74 @@ class charge_distribution_surface : public Lyt strg->phys_params = params; this->initialize_nm_distance_matrix(); this->initialize_potential_matrix(); - strg->charge_index.second = params.base; - strg->max_charge_index = static_cast(std::pow(strg->phys_params.base, this->num_cells())) - 1; + strg->charge_index_and_base.second = params.base; + strg->max_charge_index = static_cast(std::pow(strg->phys_params.base, this->num_cells())) - 1; this->update_local_potential(); this->recompute_system_energy(); this->validity_check(); } } /** - * Delete the assign_cell_type function of the underlying layout. + * This function retrieves the physical parameters of the simulation. + * + * @return sidb_simulation_parameters struct containing the physical parameters of the simulation. */ - void assign_cell_type(const typename Lyt::cell& c, const typename Lyt::cell_type& ct) = delete; + [[nodiscard]] sidb_simulation_parameters get_phys_params() const noexcept + { + return strg->phys_params; + } + /** - * Check if any SiDB exhibits the given charge state. + * This function checks if any SiDB exhibits the given charge state. * * @param cs Charge state. */ [[nodiscard]] bool charge_exists(const sidb_charge_state& cs) const noexcept { - return std::any_of(strg->cell_charge.begin(), strg->cell_charge.end(), + return std::any_of(strg->cell_charge.cbegin(), strg->cell_charge.cend(), [&cs](const sidb_charge_state& c) { return c == cs; }); } /** - * Retrieves the physical parameters of the simulation. + * This function searches the index of an SiDB. * - * @return sidb_simulation_parameters struct containing the physical parameters of the simulation. + * @param c The cell to find the index of. + * @return The index of the cell in the layout. Returns -1 if the cell is not part of the layout. */ - [[nodiscard]] sidb_simulation_parameters get_phys_params() const noexcept + [[nodiscard]] int64_t cell_to_index(const typename Lyt::cell& c) const noexcept { - return strg->phys_params; + if (const auto it = std::find(strg->sidb_order.cbegin(), strg->sidb_order.cend(), c); + it != strg->sidb_order.cend()) + { + return static_cast(std::distance(strg->sidb_order.cbegin(), it)); + } + + return -1; + } + /** + * This function assigns the given charge state to the given cell of the layout. + * + * @param c The cell to which a charge state is to be assigned. + * @param cs The charge state to be assigned to the cell. + * @param update_charge_index `true` if the charge index should be changed, `false` otherwise. + */ + void assign_charge_state(const typename Lyt::cell& c, const sidb_charge_state& cs, + const bool update_charge_index = true) const noexcept + { + if (auto index = cell_to_index(c); index != -1) + { + strg->cell_charge[static_cast(index)] = cs; + } + if (update_charge_index) + { + this->charge_distribution_to_index(); + } } /** * This function assigns the given charge state to the cell of the layout at the specified index. It updates the * `cell_charge` member of `strg` object with the new charge state of the specified cell. * * @param i The index of the cell. - * @param cs The charge state to be assigned to the cell. + * @param cs The charge state to be assign to the cell. */ void assign_charge_by_cell_index(const uint64_t i, const sidb_charge_state& cs) const noexcept { @@ -282,95 +415,136 @@ class charge_distribution_surface : public Lyt this->charge_distribution_to_index(); } /** - * This function assigns the given charge state to the given cell of the layout. + * This function assigns the charge state of all SiDBs in the layout to a given charge state. * - * @param c The cell to which a charge state is to be assigned. - * @param cs The charge state to be assigned to the cell. + * @param cs The charge state to be assigned to all the SiDBs. */ - void assign_charge_state(const typename Lyt::cell& c, const sidb_charge_state& cs) const noexcept + void assign_all_charge_states(const sidb_charge_state& cs) noexcept { - if (auto index = cell_to_index(c); index != -1) + for (uint64_t i = 0u; i < strg->cell_charge.size(); ++i) { - strg->cell_charge[static_cast(index)] = cs; + strg->cell_charge[i] = cs; } - this->charge_distribution_to_index(); } /** - * This function assigns the given charge state to the cell (accessed by `index`) of the layout. + * This function assigns the base number for the simulation. * - * @param index The index of the cell to which a charge state is to be assigned. - * @param cs The charge state to be assigned to the cell. - * @param update_chargeconf if set to `true`, the charge distribution index is updated after the charge distribution - * is changed. + * @param base Base number to be assigned. */ - void assign_charge_state_by_cell_index(const uint64_t index, const sidb_charge_state& cs, - const bool update_chargeconf = true) noexcept + void assign_base_number(const uint8_t base) noexcept { - strg->cell_charge[index] = cs; - - if (update_chargeconf) + strg->phys_params.base = base; + strg->charge_index_and_base.second = base; + if (!strg->dependent_cell.is_dead()) { - this->charge_distribution_to_index(); + strg->max_charge_index = + static_cast(std::pow(static_cast(base), this->num_cells() - 1) - 1); + } + else + { + strg->max_charge_index = static_cast(std::pow(static_cast(base), this->num_cells()) - 1); } } /** - * Sets the charge state of all SiDBs in the layout to a given charge state. + * This function adds a defect to the layout. * - * @param cs The charge state to be assigned to all the SiDBs. + * @param c The cell to which a defect is added. + * @param defect Defect which is added to the layout. */ - void set_all_charge_states(const sidb_charge_state& cs) noexcept + void add_sidb_defect_to_potential_landscape(const typename Lyt::cell& c, const sidb_defect& defect) noexcept { - for (uint64_t i = 0u; i < strg->cell_charge.size(); ++i) + // check if defect is not placed on SiDB position + if (std::find(strg->sidb_order.cbegin(), strg->sidb_order.cend(), c) == strg->sidb_order.end()) { - strg->cell_charge[i] = cs; - } + // check if defect was not added yet. + if (strg->defects.find(c) == strg->defects.end()) + { + strg->defects.insert({c, defect}); + this->foreach_cell( + [this, &c, &defect](const auto& c1) + { + const auto dist = sidb_nanometer_distance(*this, c1, c, strg->phys_params); + const auto pot = chargeless_potential_generated_by_defect_at_given_distance(dist, defect); + + if (strg->defect_local_pot.empty()) + { + strg->defect_local_pot.insert(std::make_pair(c1, pot * static_cast(defect.charge))); + } + else + { + strg->defect_local_pot[c1] += pot * static_cast(defect.charge); + } + }); + + this->update_after_charge_change(dependent_cell_mode::FIXED); + } + else + { + this->foreach_cell( + [this, &c, &defect](const auto& c1) + { + const auto dist = sidb_nanometer_distance(*this, c1, c, strg->phys_params); - this->charge_distribution_to_index(); + strg->defect_local_pot[c1] = + strg->defect_local_pot[c1] + + chargeless_potential_generated_by_defect_at_given_distance(dist, defect) * + static_cast(defect.charge) - + chargeless_potential_generated_by_defect_at_given_distance(dist, strg->defects[c]) * + static_cast(strg->defects[c].charge); + }); + + strg->defects.erase(c); + strg->defects.insert({c, defect}); + + this->update_after_charge_change(dependent_cell_mode::FIXED); + } + } } /** - * This function can be used to detect which SiDBs must be negatively charged due to their location. Important: - * This function must be applied to a charge layout where all SiDBs are negatively initialized. + * This function erases a defect to the layout. * - * @return Vector of indices describing which SiDBs must be negatively charged. + * @param c The cell where a defect is erased. */ - std::vector negative_sidb_detection() noexcept + void erase_defect(const typename Lyt::cell& c) noexcept { - std::vector negative_sidbs{}; - negative_sidbs.reserve(this->num_cells()); - this->foreach_cell( - [&negative_sidbs, this](const auto& c) - { - if (const auto local_pot = this->get_local_potential(c); local_pot.has_value()) + if (strg->defects.find(c) != strg->defects.cend()) + { + this->foreach_cell( + [this, &c](const auto& c1) { - // Check if the maximum band bending is sufficient to shift (0/-) above the Fermi level. The local - // potential is converted from J to eV to compare the band bending with the Fermi level (which is - // also given in eV). - if ((-*local_pot + strg->phys_params.mu_minus) < -physical_constants::POP_STABILITY_ERR) - { - negative_sidbs.push_back(cell_to_index(c)); - } - } - }); - return negative_sidbs; + strg->local_pot[static_cast(cell_to_index(c1))] -= + chargeless_potential_generated_by_defect_at_given_distance( + sidb_nanometer_distance(*this, c1, c, strg->phys_params), strg->defects[c]) * + static_cast(strg->defects[c].charge); + strg->defect_local_pot[c1] -= + chargeless_potential_generated_by_defect_at_given_distance( + sidb_nanometer_distance(*this, c1, c, strg->phys_params), strg->defects[c]) * + static_cast(strg->defects[c].charge); + }); + strg->defects.erase(c); + } } /** - * Returns the charge state of a cell of the layout at a given index. + * This function assigns the given charge state to the cell (accessed by `index`) of the layout. * - * @param index The index of the cell. - * @return The charge state of the cell at the given index. + * @param index The index of the cell to which a charge state is to be assigned. + * @param cs The charge state to be assigned to the cell. + * @param update_charge_configuration if set to `true`, the charge distribution index is updated after the charge + * distribution is changed. */ - [[nodiscard]] sidb_charge_state get_charge_state_by_index(const uint64_t index) const noexcept + void assign_charge_state_by_cell_index(const uint64_t index, const sidb_charge_state& cs, + const bool update_charge_configuration = true) noexcept { - if (index < (strg->cell_charge.size())) + strg->cell_charge[index] = cs; + + if (update_charge_configuration) { - return strg->cell_charge[index]; + this->charge_distribution_to_index(); } - - return sidb_charge_state::NONE; } /** - * Returns the charge state of a given cell. + * This function returns the charge state of a given cell. * * @param c The cell. * @return The charge state of the given cell. @@ -385,23 +559,57 @@ class charge_distribution_surface : public Lyt return sidb_charge_state::NONE; } /** - * Finds the index of an SiDB. + * This function returns the charge state of a cell of the layout at a given index. * - * @param c The cell to find the index of. - * @return The index of the cell in the layout. Returns -1 if the cell is not part of the layout. + * @param index The index of the cell. + * @return The charge state of the cell at the given index. */ - [[nodiscard]] int64_t cell_to_index(const typename Lyt::cell& c) const noexcept + [[nodiscard]] sidb_charge_state get_charge_state_by_index(const uint64_t index) const noexcept { - if (const auto it = std::find(strg->sidb_order.cbegin(), strg->sidb_order.cend(), c); - it != strg->sidb_order.cend()) + if (index < (strg->cell_charge.size())) { - return static_cast(std::distance(strg->sidb_order.cbegin(), it)); + return strg->cell_charge[index]; } - return -1; + return sidb_charge_state::NONE; + } + /** + * This function returns all SiDB charges of the placed SiDBs as a vector. + * + * @return Vector of SiDB charge states. + */ + [[maybe_unused]] [[nodiscard]] std::vector get_all_sidb_charges() const noexcept + { + return strg->cell_charge; } /** - * Returns the distance between two cells. + * This function can be used to detect which SiDBs must be negatively charged due to their location. Important: + * This function must be applied to a charge layout where all SiDBs are negatively initialized. + * + * @return Vector of indices describing which SiDBs must be negatively charged. + */ + std::vector negative_sidb_detection() noexcept + { + std::vector negative_sidbs{}; + negative_sidbs.reserve(this->num_cells()); + this->foreach_cell( + [&negative_sidbs, this](const auto& c) + { + if (const auto local_pot = this->get_local_potential(c); local_pot.has_value()) + { + // Check if the maximum band bending is sufficient to shift (0/-) above the Fermi level. The local + // potential is converted from J to eV to compare the band bending with the Fermi level (which is + // also given in eV). + if ((-*local_pot + strg->phys_params.mu_minus) < -physical_constants::POP_STABILITY_ERR) + { + negative_sidbs.push_back(cell_to_index(c)); + } + } + }); + return negative_sidbs; + } + /** + * This function returns the distance between two cells in nanometer (unit: nm). * * @param c1 the first cell to compare. * @param c2 the second cell to compare. @@ -418,18 +626,20 @@ class charge_distribution_surface : public Lyt return 0.0; } /** - * Calculates and returns the distance between two cells (accessed by indices). + * This function calculates and returns the distance between two cells in nanometer (accessed by indices) (unit: + * nm). * * @param index1 The first index. * @param index2 The second index. - * @return The distance index between `index1` and `index2` (indices correspond to unique SiDBs) (unit: nm). + * @return The distance in nanometer between `index1` and `index2` (indices correspond to unique SiDBs) (unit: nm). */ [[nodiscard]] double get_nm_distance_by_indices(const uint64_t index1, const uint64_t index2) const noexcept { return strg->nm_dist_mat[index1][index2]; } /** - * The chargeless electrostatic potential between two cells (SiDBs) is calculated in Volt. + * This function calculates and returns the chargeless electrostatic potential between two cells (SiDBs) in Volt + * (unit: V). * * @param index1 The first index. * @param index1 The second index. @@ -450,12 +660,12 @@ class charge_distribution_surface : public Lyt physical_constants::ELEMENTARY_CHARGE); } /** - * Calculates and returns the chargeless potential of a pair of cells based on their distance and simulation - * parameters. + * This function calculates and returns the chargeless potential in Volt of a pair of cells based on their distance + * and simulation parameters (unit: V). * * @param c1 The first cell. * @param c2 The second cell. - * @return The potential between c1 and c2 (unit: eV). + * @return The potential between c1 and c2 (unit: V). */ [[nodiscard]] double calculate_chargeless_potential_between_sidbs(const typename Lyt::cell& c1, const typename Lyt::cell& c2) const noexcept @@ -466,7 +676,7 @@ class charge_distribution_surface : public Lyt return calculate_chargeless_potential_between_sidbs_by_index(index1, index2); } /** - * Returns the chargeless electrostatic potential between two cells. + * This function returns the chargeless electrostatic potential between two cells in V (unit: V). * * @note If the signed electrostatic potential \f$ V_{i,j} \f$ is required, use the `get_potential_between_sidbs` * function. @@ -486,7 +696,8 @@ class charge_distribution_surface : public Lyt return 0.0; } /** - * Calculates and returns the potential of two indices. + * This function calculates and returns the chargeless potential of two indices (representing two SiDBs) in Volt + * (unit: V). * * @param index1 The first index. * @param index2 The second index. @@ -497,7 +708,8 @@ class charge_distribution_surface : public Lyt return strg->pot_mat[index1][index2]; } /** - * Calculates and returns the electrostatic potential at one cell (`c1`) generated by another cell (`c2`). + * This function calculates and returns the electrostatic potential at one cell (`c1`) generated by another cell + * (`c2`) in Volt (unit: V). * * @note If the chargeless electrostatic potential \f$ \frac{V_{i,j}}{n_j} \f$ is required, use the * `get_chargeless_potential_between_sidbs` function. @@ -518,76 +730,208 @@ class charge_distribution_surface : public Lyt return 0.0; } /** - * The function calculates the electrostatic potential for each SiDB position (local). + * This function calculates and returns the electrostatic potential of two indices (representing two SiDBs) in Volt + * (unit: V). + * + * @param index1 The first index. + * @param index2 The second index. + * @return The potential between `index1` and `index2` (unit: V). */ - void update_local_potential() noexcept + [[nodiscard]] double get_electrostatic_potential_by_indices(const uint64_t index1, + const uint64_t index2) const noexcept { - strg->loc_pot.resize(this->num_cells(), 0.0); - - for (uint64_t i = 0u; i < strg->sidb_order.size(); ++i) - { - double collect = 0.0; - for (uint64_t j = 0u; j < strg->sidb_order.size(); j++) - { - collect += strg->pot_mat[i][j] * static_cast(charge_state_to_sign(strg->cell_charge[j])); - } - - strg->loc_pot[i] = collect; - } + return strg->pot_mat[index1][index2]; } /** - * The function returns the local electrostatic potential at a given SiDB position in V. + * The electrostatic potential in Volt between two cells (SiDBs) is calculated and returned (unit: V). * - * @param c The cell defining the SiDB position. - * @return Local potential at given cell position. If there is no SiDB at the given cell, `std::nullopt` is - * returned (unit: V). + * @param index1 The first index. + * @param index1 The second index. + * @return The potential between `index1` and `index2` (unit: V). */ - std::optional get_local_potential(const typename Lyt::cell& c) const noexcept + [[nodiscard]] double potential_between_sidbs_by_index(const uint64_t index1, const uint64_t index2) const noexcept { - if (const auto index = cell_to_index(c); index != -1) + if (strg->nm_dist_mat[index1][index2] == 0.0) { - return strg->loc_pot[static_cast(index)]; + return 0.0; } - return std::nullopt; + return (strg->phys_params.k / (strg->nm_dist_mat[index1][index2] * 1E-9) * + std::exp(-strg->nm_dist_mat[index1][index2] / strg->phys_params.lambda_tf) * + physical_constants::ELEMENTARY_CHARGE); } /** - * The function returns the local electrostatic potential at a given index position in V. + * This function calculates and returns the electrostatic potential in Volt of a pair of cells based on their + * distance and simulation parameters (unit: V). * - * @param index The index defining the SiDB position. - * @return local potential at given index position. If there is no SiDB at the given index (which corresponds to a - * unique cell), `std::nullopt` is returned (unit: V). + * @param c1 The first cell. + * @param c2 The second cell. + * @return The electrostatic potential between c1 and c2 (unit: V). */ - [[nodiscard]] std::optional get_local_potential_by_index(const uint64_t index) const noexcept + [[nodiscard]] double potential_between_sidbs(const typename Lyt::cell& c1, + const typename Lyt::cell& c2) const noexcept { - if (index < strg->sidb_order.size()) - { - return strg->loc_pot[index]; - } - return std::nullopt; - } + const auto index1 = static_cast(cell_to_index(c1)); + const auto index2 = static_cast(cell_to_index(c2)); + + return potential_between_sidbs_by_indices(index1, index2); + } /** - * Sets the electrostatic system energy to zero. Can be used if only one SiDB is charged. + * This function calculates and returns the electrostatic potential in Volt between two cells (SiDBs) (unit: V). + * + * @param index1 The first index. + * @param index1 The second index. + * @return The electrostatic potential between `index1` and `index2` (unit: V). */ - void set_system_energy_to_zero() noexcept + [[nodiscard]] double potential_between_sidbs_by_indices(const uint64_t index1, const uint64_t index2) const noexcept + { + if (strg->nm_dist_mat[index1][index2] == 0.0) + { + return 0.0; + } + + return (strg->phys_params.k / (strg->nm_dist_mat[index1][index2] * 1E-9) * + std::exp(-strg->nm_dist_mat[index1][index2] / strg->phys_params.lambda_tf) * + physical_constants::ELEMENTARY_CHARGE); + } + + /** + * This function calculates the local electrostatic potential in Volt for each SiDB position, including external + * electrostatic potentials (generated by electrodes, defects, etc.) (unit: V). + * + * @param history_mode If set to NEGLECT, the local electrostatic is calculated from scratch, without using the + * results of the previous charge distribution. + */ + void update_local_potential( + const charge_distribution_history& history_mode = charge_distribution_history::NEGLECT) noexcept + { + if (history_mode == charge_distribution_history::NEGLECT) + { + strg->local_pot.resize(this->num_cells(), 0); + + for (uint64_t i = 0u; i < strg->sidb_order.size(); ++i) + { + double collect = 0.0; + for (uint64_t j = 0u; j < strg->sidb_order.size(); j++) + { + collect += strg->pot_mat[i][j] * static_cast(charge_state_to_sign(strg->cell_charge[j])); + } + + strg->local_pot[i] = collect; + } + + for (const auto& [cell, defect_pot] : strg->defect_local_pot) + { + strg->local_pot[static_cast(cell_to_index(cell))] += defect_pot; + } + + for (const auto& [cell, external_pot] : strg->local_external_pot) + { + strg->local_pot[static_cast(cell_to_index(cell))] += external_pot; + } + } + else + { + if (strg->phys_params.base == 2) + { + if (strg->cell_history_gray_code.first != -1) + { + const auto cell_charge = + charge_state_to_sign(strg->cell_charge[strg->cell_history_gray_code.first]); + const auto charge_diff = static_cast(cell_charge - strg->cell_history_gray_code.second); + for (uint64_t j = 0u; j < strg->sidb_order.size(); j++) + { + strg->local_pot[j] += strg->pot_mat[strg->cell_history_gray_code.first][j] * charge_diff; + } + } + } + else + { + for (const auto& [changed_cell, charge] : strg->cell_history) + { + for (uint64_t j = 0u; j < strg->sidb_order.size(); j++) + { + strg->local_pot[j] += + strg->pot_mat[changed_cell][j] * + (static_cast(charge_state_to_sign(strg->cell_charge[changed_cell])) - charge); + } + } + } + } + } + /** + * The function returns the local electrostatic potential at a given SiDB position in V. + * + * @param c The cell defining the SiDB position. + * @return Local potential at given cell position. If there is no SiDB at the given cell, `std::nullopt` is + * returned (unit: V). + */ + std::optional get_local_potential(const typename Lyt::cell& c) const noexcept + { + if (const auto index = cell_to_index(c); index != -1) + { + return strg->local_pot[static_cast(index)]; + } + + return std::nullopt; + } + /** + * This function returns the local electrostatic potential at a given index position in Volt (unit: V). + * + * @param index The index defining the SiDB position. + * @return local potential at given index position. If there is no SiDB at the given index (which corresponds to a + * unique cell), `std::nullopt` is returned (unit: V). + */ + [[nodiscard]] std::optional get_local_potential_by_index(const uint64_t index) const noexcept + { + if (index < strg->sidb_order.size()) + { + return strg->local_pot[index]; + } + return std::nullopt; + } + /** + * This function assign the electrostatic system energy to zero (unit: eV). It can be used if only one SiDB is + * charged. + */ + void assign_system_energy_to_zero() noexcept { strg->system_energy = 0.0; } /** - * Calculates the system's total electrostatic potential energy and stores it in the storage. + * This function calculates the system's total electrostatic potential energy and stores it in the storage (unit: + * eV). */ void recompute_system_energy() noexcept { double total_potential = 0.0; - for (uint64_t i = 0; i < strg->loc_pot.size(); ++i) + for (uint64_t i = 0; i < strg->local_pot.size(); ++i) + { + total_potential += + 0.5 * strg->local_pot[i] * static_cast(charge_state_to_sign(strg->cell_charge[i])); + } + + double defect_energy = 0; + for (const auto& [cell, pot] : strg->defect_local_pot) { - total_potential += 0.5 * strg->loc_pot[i] * charge_state_to_sign(strg->cell_charge[i]); + defect_energy += pot * static_cast(charge_state_to_sign( + strg->cell_charge[static_cast(cell_to_index(cell))])); } - strg->system_energy = total_potential; + + double defect_interaction = 0; + for (const auto& [cell1, defect1] : strg->defects) + { + for (const auto& [cell2, defect2] : strg->defects) + { + defect_interaction += chargeless_potential_at_given_distance( + sidb_nanometer_distance(*this, cell1, cell2, strg->phys_params)); + } + } + strg->system_energy = total_potential + 0.5 * defect_energy + 0.5 * defect_interaction; } /** - * Return the currently stored system's total electrostatic potential energy in eV. + * This function returns the currently stored system's total electrostatic potential energy in eV. * * @return The system's total electrostatic potential energy (unit: eV). */ @@ -596,12 +940,30 @@ class charge_distribution_surface : public Lyt return strg->system_energy; } /** - * The function updates the local potential and the system energy after a charge change. + * The function updates the local potential (unit: Volt) and the system energy (unit: eV) after a charge change. + * + * @param dependent_cell dependent_cell_mode::FIXED if the state of the dependent cell should not change, + * dependent_cell_mode::VARIABLE if it should. + * @param energy_calculation_mode energy_calculation::UPDATE_ENERGY if the electrostatic potential energy should be + * updated, energy_calculation::KEEP_ENERGY otherwise. + * @param history_mode charge_distribution_history::NEGLECT if the information (local electrostatic energy) of the + * previous charge distribution is used to make the update more efficient, charge_distribution_history::CONSIDER + * otherwise. */ - void update_after_charge_change() noexcept + void update_after_charge_change( + const dependent_cell_mode& dependent_cell = dependent_cell_mode::FIXED, + const energy_calculation& energy_calculation_mode = energy_calculation::UPDATE_ENERGY, + const charge_distribution_history& history_mode = charge_distribution_history::NEGLECT) noexcept { - this->update_local_potential(); - this->recompute_system_energy(); + this->update_local_potential(history_mode); + if (dependent_cell == dependent_cell_mode::VARIABLE) + { + this->update_charge_state_of_dependent_cell(); + } + if (energy_calculation_mode == energy_calculation::UPDATE_ENERGY) + { + this->recompute_system_energy(); + } this->validity_check(); } /** @@ -614,7 +976,7 @@ class charge_distribution_surface : public Lyt uint64_t for_loop_counter = 0; const auto mu_p = strg->phys_params.mu_plus(); - for (const auto& it : strg->loc_pot) // this for-loop checks if the "population stability" is fulfilled. + for (const auto& it : strg->local_pot) // this for-loop checks if the "population stability" is fulfilled. { bool valid = (((strg->cell_charge[for_loop_counter] == sidb_charge_state::NEGATIVE) && (-it + strg->phys_params.mu_minus < physical_constants::POP_STABILITY_ERR)) || @@ -643,18 +1005,18 @@ class charge_distribution_surface : public Lyt const int dn_i = (strg->cell_charge[c1] == sidb_charge_state::NEGATIVE) ? 1 : -1; const int dn_j = -dn_i; - return strg->loc_pot[c1] * dn_i + strg->loc_pot[c2] * dn_j - strg->pot_mat[c1][c2] * 1; + return strg->local_pot[c1] * dn_i + strg->local_pot[c2] * dn_j - strg->pot_mat[c1][c2] * 1; }; uint64_t hop_counter = 0; - for (uint64_t i = 0u; i < strg->loc_pot.size(); ++i) + for (uint64_t i = 0u; i < strg->local_pot.size(); ++i) { if (strg->cell_charge[i] == sidb_charge_state::POSITIVE) // we do nothing with SiDB+ { continue; } - for (uint64_t j = 0u; j < strg->loc_pot.size(); j++) + for (uint64_t j = 0u; j < strg->local_pot.size(); j++) { if (hop_counter == 1) { @@ -679,7 +1041,7 @@ class charge_distribution_surface : public Lyt } } /** - * Returns the currently stored validity of the present charge distribution layout. + * This function returns the currently stored validity of the present charge distribution layout. * * @returns The validity of the present charge distribution. */ @@ -687,72 +1049,195 @@ class charge_distribution_surface : public Lyt { return strg->validity; } + /** * The charge distribution of the charge distribution surface is converted to a unique index. It is used to map * every possible charge distribution of an SiDB layout to a unique index. + * + * IMPORTANT: This function can be used whenever a charge distribution needs to be converted to a charge index. + * However, this function is not optimized compared to charge_distribution_to_index. */ - void charge_distribution_to_index() const noexcept + void charge_distribution_to_index_general() const noexcept { const uint8_t base = strg->phys_params.base; uint64_t chargeindex = 0; uint64_t counter = 0; - for (const auto& c : strg->cell_charge) + for (const auto& cell : strg->sidb_order) { chargeindex += - static_cast((charge_state_to_sign(c) + 1) * std::pow(base, this->num_cells() - counter - 1)); + static_cast( + charge_state_to_sign(strg->cell_charge[static_cast(cell_to_index(cell))]) + int8_t{1}) * + static_cast(std::pow(base, this->num_cells() - 1u - counter)); counter += 1; } - strg->charge_index = {chargeindex, base}; + strg->charge_index_and_base = {chargeindex, base}; } /** - * The charge index of the current charge distribution is returned. - * - * @return A pair with the charge index and the used base is returned. - */ - [[nodiscard]] charge_index_base get_charge_index() const noexcept - { - return strg->charge_index; - } - /** - * The stored unique index is converted to the charge distribution of the charge distribution surface. + * The charge distribution of the charge distribution surface is converted to a unique index. It is used to map + * every possible charge distribution of an SiDB layout to a unique index. */ - void index_to_charge_distribution() noexcept + void charge_distribution_to_index() const noexcept { - auto charge_quot = strg->charge_index.first; - const auto base = strg->charge_index.second; - const auto num_charges = this->num_cells() - 1; - auto counter = num_charges; + const uint8_t base = strg->phys_params.base; - while (charge_quot > 0) - { - const std::div_t d = std::div(static_cast(charge_quot), static_cast(base)); - charge_quot = static_cast(d.quot); + uint64_t chargeindex = 0; + uint64_t counter = 0; + + uint64_t chargeindex_sub_layout = 0; + uint64_t counter_sub_layout = 0; - this->assign_charge_state_by_cell_index(counter, sign_to_charge_state(static_cast(d.rem - 1)), - false); + if (!strg->dependent_cell.is_dead()) + { + if (!strg->three_state_cells.empty()) + { + if (strg->dependent_cell_in_sub_layout) + { + // iterate through all cells that can be positively charged + for (const auto& cell : strg->three_state_cells) + { + if (cell != strg->dependent_cell) + { + chargeindex_sub_layout += + static_cast( + (charge_state_to_sign( + strg->cell_charge[static_cast(cell_to_index(cell))]) + + int8_t{1})) * + static_cast( + std::pow(3, strg->three_state_cells.size() - counter_sub_layout - 1)); + counter_sub_layout += 1u; + } + } + // iterate through all cells that cannot be positively charged + for (const auto& cell : strg->sidb_order_without_three_state_cells) + { + chargeindex += static_cast( + (charge_state_to_sign(strg->cell_charge[static_cast(cell_to_index(cell))]) + 1) * + std::pow(2, this->num_cells() - 1 - counter - 1)); + counter += 1; + } + } + // dependent-cell is not among the SiDBs that can be positively charged + else + { + for (const auto& cell : strg->three_state_cells) + { + chargeindex_sub_layout += + static_cast( + charge_state_to_sign(strg->cell_charge[static_cast(cell_to_index(cell))]) + + int8_t{1}) * + static_cast(std::pow(3, strg->three_state_cells.size() - counter_sub_layout - 1)); + counter_sub_layout += 1; + } + for (const auto& cell : strg->sidb_order_without_three_state_cells) + { + if (cell != strg->dependent_cell) + { + chargeindex += static_cast( + (charge_state_to_sign( + strg->cell_charge[static_cast(cell_to_index(cell))]) + + int8_t{1})) * + static_cast(std::pow(2, this->num_cells() - 1 - counter - 1)); + counter += 1; + } + } + } + } + // there are no positively charged SiDBs + else + { + for (uint64_t c = 0; c < strg->cell_charge.size(); c++) + { + if (c != static_cast(cell_to_index(strg->dependent_cell))) + { + chargeindex += static_cast((charge_state_to_sign(strg->cell_charge[c]) + 1) * + std::pow(base, this->num_cells() - 1 - counter - 1)); + counter += 1; + } + } + } + } + // there is no dependent-cell chosen + else + { + // there are SiDBs that can be positively charged + if (!strg->three_state_cells.empty()) + { + // iterate through SiDBs that can be positively charged + for (const auto& cell : strg->three_state_cells) + { - counter -= 1; + chargeindex_sub_layout += static_cast( + (charge_state_to_sign(strg->cell_charge[static_cast(cell_to_index(cell))]) + 1) * + std::pow(3, strg->three_state_cells.size() - 1 - counter_sub_layout)); + counter_sub_layout += 1; + } + // iterate through SiDBs that cannot be positively charged + for (const auto& cell : strg->sidb_order_without_three_state_cells) + { + chargeindex += static_cast( + (charge_state_to_sign(strg->cell_charge[static_cast(cell_to_index(cell))]) + 1) * + std::pow(2, this->num_cells() - 1 - counter)); + counter += 1; + } + } + // there are no SiDBs that can be positively charged + else + { + for (const auto& cell : strg->sidb_order) + { + chargeindex += static_cast( + (charge_state_to_sign(strg->cell_charge[static_cast(cell_to_index(cell))]) + 1) * + std::pow(base, this->num_cells() - 1 - counter)); + counter += 1; + } + } } + + strg->charge_index_and_base = {chargeindex, base}; + strg->charge_index_sublayout = chargeindex_sub_layout; + } + /** + * The charge index of the current charge distribution is returned. + * + * @return A pair with the charge index and the used base. + */ + [[nodiscard]] charge_index_base get_charge_index_and_base() const noexcept + { + return strg->charge_index_and_base; } /** * The charge index is increased by one, but only if it is less than the maximum charge index for the given layout. * If that's the case, it is increased by one and afterward, the charge configuration is updated by invoking the * `index_to_charge_distribution()` function. + * + * @param dependent_cell dependent_cell_mode::FIXED if the state of the dependent cell should not change, + * dependent_cell_mode::VARIABLE if it should. + * @param energy_calculation_mode energy_calculation::UPDATE_ENERGY if the electrostatic potential energy should be + * updated, energy_calculation::KEEP_ENERGY otherwise. + * @param history_mode charge_distribution_history::NEGLECT if the information (local electrostatic energy) of the + * previous charge distribution is used to make the update more efficient, charge_distribution_history::CONSIDER + * otherwise. + * @param engine exhaustive_algorithm::EXGS if `ExGS``should be used, exhaustive_algorithm::QUICKEXACT for + * `QuickExact`. */ - void increase_charge_index_by_one() noexcept + void increase_charge_index_by_one( + const dependent_cell_mode& dependent_cell_fixed = dependent_cell_mode::FIXED, + const energy_calculation& recompute_system_energy = energy_calculation::UPDATE_ENERGY, + const charge_distribution_history& consider_history = charge_distribution_history::NEGLECT, + const exhaustive_algorithm& engine = exhaustive_algorithm::EXGS) noexcept { - if (strg->charge_index.first < strg->max_charge_index) + if (strg->charge_index_and_base.first < strg->max_charge_index) { - strg->charge_index.first += 1; - this->index_to_charge_distribution(); - this->update_after_charge_change(); + strg->charge_index_and_base.first += 1; + this->index_to_charge_distribution(engine); + this->update_after_charge_change(dependent_cell_fixed, recompute_system_energy, consider_history); } } /** - * Returns the maximum index of the cell-level layout. + * This function returns the maximum index of the cell-level layout. * * @returns The maximal possible charge distribution index. */ @@ -761,12 +1246,15 @@ class charge_distribution_surface : public Lyt return strg->max_charge_index; } /** - * Assigns a certain charge state to a given index (which corresponds to a certain SiDB) and the charge distribution - * is updated correspondingly. + * Assigns a given charge index to the charge distribution layout. Charge distribution is updated + * according to the set charge index. + * + * @param charge_index charge index of the new charge distribution. */ - void assign_charge_index(const uint64_t index) noexcept + void assign_charge_index(const uint64_t charge_index) noexcept { - strg->charge_index.first = index; + assert((charge_index <= strg->max_charge_index) && "charge index is too large"); + strg->charge_index_and_base.first = charge_index; this->index_to_charge_distribution(); } /** @@ -832,10 +1320,492 @@ class charge_distribution_surface : public Lyt for (uint64_t i = 0u; i < strg->pot_mat.size(); ++i) { - strg->loc_pot[i] += -(this->get_chargless_potential_by_indices(i, random_element)); + strg->local_pot[i] += -(this->get_chargless_potential_by_indices(i, random_element)); + } + } + } + /** + * This function can be used to assign a global external electrostatic potential in Volt (unit: V) to the layout + * (e.g this could be a planar external electrode). + * + * @param potential_value Value of the global external electrostatic potential in Volt (e.g. -0.3). + * Charge-transition levels are shifted by this value. + * @param dependent_cell dependent_cell_mode::FIXED if the state of the dependent cell should not change, + * dependent_cell_mode::VARIABLE if it should. + */ + void assign_global_external_potential(const double potential_value, + dependent_cell_mode dependent_cell = dependent_cell_mode::FIXED) noexcept + { + this->foreach_cell( + [this, &potential_value](const auto& cell) { + strg->local_external_pot.insert({cell, potential_value}); + }); + this->update_after_charge_change(dependent_cell); + } + /** + * This function determines if given layout has to be simulated with three states since positively charged SiDBs can + * occur due to the local potential analysis. In addition, all SiDBs that can be positively charged are collected. + * + * @note All SiDBs have to be set to negatively charged. + * + * @return return value is true when three state simulation is required. + */ + bool is_three_state_simulation_required() noexcept + { + this->update_after_charge_change(); + strg->three_state_cells = {}; + strg->sidb_order_without_three_state_cells = {}; + bool required = false; + strg->dependent_cell_in_sub_layout = false; + + // check if all SiDBs are negatively charged + this->foreach_cell( + [this](const auto& c) { + assert(this->get_charge_state(c) == sidb_charge_state::NEGATIVE && + "All SiDBs have to be negatively charged"); + }); + + // Each SiDB is checked to see if the local electrostatic potential is high enough + // to cause a positively charged SiDB + this->foreach_cell( + [&required, this](const auto& c) + { + if (const auto local_pot = this->get_local_potential(c); local_pot.has_value()) + { + if ((-(*local_pot) + strg->phys_params.mu_plus()) > -physical_constants::POP_STABILITY_ERR) + { + if (c == strg->dependent_cell) + { + strg->dependent_cell_in_sub_layout = true; + } + else + { + strg->three_state_cells.emplace_back(c); + required = true; + } + } + } + }); + + // sort all SiDBs that can be positively charged by the defined < relation + std::sort(strg->three_state_cells.begin(), strg->three_state_cells.end()); + + // collect all SiDBs that are not among the SiDBs that can be positively charged + for (const auto& cell : strg->sidb_order) + { + if (std::find(strg->three_state_cells.cbegin(), strg->three_state_cells.cend(), cell) == + strg->three_state_cells.end() && + cell != strg->dependent_cell) + { + strg->sidb_order_without_three_state_cells.push_back(cell); + } + } + + // sort all SiDBs that cannot be positively charged by the defined < relation + std::sort(strg->sidb_order_without_three_state_cells.begin(), strg->sidb_order_without_three_state_cells.end()); + + // if SiDBs exist that can be positively charged, 3-state simulation is required + if (required) + { + this->assign_base_number_to_three(); + } + + return required; + } + /** + * This functions returns all cells that could be positively charged. However, this must not be necessarily the case + * in a physically valid layout. + * + * @return All cell that could be positively charged. + */ + std::vector get_positive_candidates() const noexcept + { + return strg->three_state_cells; + } + /** + * This function searches the index of a cell which is part of the sublayout (i.e. it should be a cell which can be + * positively charged). + * + * @param c Cell that should be part of the sublayout. + * @return Index (i.e. position in the vector) of the input cell. + */ + [[nodiscard]] int64_t positive_cell_to_index(const typename Lyt::cell& c) const noexcept + { + if (const auto it = std::find(strg->three_state_cells.cbegin(), strg->three_state_cells.cend(), c); + it != strg->three_state_cells.cend()) + { + return static_cast(std::distance(strg->three_state_cells.cbegin(), it)); + } + + return -1; + } + /** + * This function searches the index of a cell which is not part of the sublayout (i.e. it should be a cell which is + * either neutrally or negatively charged). + * + * @param c Cell that should not be part of the sublayout. + * @return Index (i.e. position in the vector) of the input cell. + */ + [[nodiscard]] int64_t two_state_cell_to_index(const typename Lyt::cell& c) const noexcept + { + if (const auto it = std::find(strg->sidb_order_without_three_state_cells.cbegin(), + strg->sidb_order_without_three_state_cells.cend(), c); + it != strg->sidb_order_without_three_state_cells.cend()) + { + return static_cast(std::distance(strg->sidb_order_without_three_state_cells.cbegin(), it)); + } + + return -1; + } + /** + * This function searches the cell of a given index. + * + * @param c The index to find the cell of. + * @return The cell in the layout for the given index. Returns dead-coordinate if the index is not assigned to a not + * empty cell in the layout. + */ + [[nodiscard]] typename Lyt::cell index_to_cell(const uint64_t index) const noexcept + { + if (index < strg->sidb_order.size()) + { + return strg->sidb_order[index]; + } + + return {}; + } + /** + * This function finds the cell for a given index which is a candidate to be positively charged of a given index. + * + * @param index The index to find the cell of (cell is candidate to be positively charged). + * @return Positive cell candidate. Dead-coordinate is returned if the index is not assigned to a not + * empty cell in the layout. + */ + [[nodiscard]] typename Lyt::cell index_to_three_state_cell(const uint64_t index) const noexcept + { + if (index < strg->three_state_cells.size()) + { + return strg->three_state_cells[index]; + } + + return {}; + } + /** + * This function finds the cell which can only be neutrally or negatively charged of a given index. + * + * @param index The index to find the cell of. + * @return The cell (which cannot be positively charged) in the layout for the given index. Dead-coordinate is + * returned if the index is not assigned to a not empty cell in the layout. + */ + [[nodiscard]] typename Lyt::cell index_to_two_state_cell(const uint64_t index) const noexcept + { + if (index < strg->sidb_order_without_three_state_cells.size()) + { + return strg->sidb_order_without_three_state_cells[index]; + } + + return {}; + } + /** + * This function calculates and returns the chargeless electrostatic potential in Volt for a given distance in + * nanometer. + * + * @param distance Distance in nanometer between position and defect (unit: nm). + * @return The chargeless electrostatic potential at a given distance (unit: V). + */ + [[nodiscard]] double chargeless_potential_at_given_distance(const double distance) const noexcept + { + if (distance == 0.0) + { + return 0.0; + } + + return (strg->phys_params.k() / (distance * 1E-9) * std::exp(-distance / strg->phys_params.lambda_tf) * + physical_constants::ELEMENTARY_CHARGE); + } + /** + * This function calculates the chargeless potential in Volt generated by a defect at a given distance in nanometer. + * + * @param distance Distance between position and defect (unit: nm. + * @param sidb_defect Defect (including defect specific parameters). + * @return The chargeless electrostatic potential in Volt generated by the defect at a given distance (unit: V). + */ + [[nodiscard]] double + chargeless_potential_generated_by_defect_at_given_distance(const double distance, + const sidb_defect& defect = sidb_defect{}) const noexcept + { + if (distance == 0.0) + { + return 0.0; + } + + return strg->phys_params.k() * strg->phys_params.epsilon_r / defect.epsilon_r / (distance * 1e-9) * + std::exp(-distance / defect.lambda_tf) * physical_constants::ELEMENTARY_CHARGE; + } + /** + * This function can be used to assign an external local electrostatic potential in Volt to the layout. All + * important attributes of the charge layout are updated automatically. + * + * @param cell Cell to which the local external potential is applied. + * @param external_voltage External electrostatic potential in Volt applied to different cells. + */ + void + assign_local_external_potential(const std::unordered_map& external_potential) noexcept + { + strg->local_external_pot = external_potential; + this->update_after_charge_change(); + } + /** + * This function returns the external electrostatic potential in Volt applied to the layout. + * + * @return External electrostatic potential as unordered map. The cell is used as key and the external electrostatic + * potential in Volt (unit: V) at its position as value. + */ + std::unordered_map get_external_potentials() noexcept + { + return strg->local_external_pot; + } + /** + * This function returns the local electrostatic potentials which are generated by defects. + * + * @return Local electrostatic potential in Volt generated by the defects at each each cell. + */ + std::unordered_map get_defect_potentials() noexcept + { + return strg->defect_local_pot; + } + /** + * This function returns the defects. + * + * @return Placed defects with cell position and type. + */ + std::unordered_map get_defects() noexcept + { + return strg->defects; + } + /** + * The charge state of the dependent-SiDB is updated based on the local electrostatic potential at its position. All + * other local electrostatic potentials are then also updated if the charge state of the dependent-SiDB has changed. + */ + void update_charge_state_of_dependent_cell() noexcept + { + if (!strg->dependent_cell.is_dead()) + { + const auto loc_pot_cell = -strg->local_pot[strg->dependent_cell_index]; + if ((loc_pot_cell + strg->phys_params.mu_minus) < physical_constants::POP_STABILITY_ERR) + { + if (strg->cell_charge[strg->dependent_cell_index] != sidb_charge_state::NEGATIVE) + { + const auto charge_diff = (-charge_state_to_sign(strg->cell_charge[strg->dependent_cell_index]) - 1); + for (uint64_t i = 0u; i < strg->pot_mat.size(); ++i) + { + if (i != strg->dependent_cell_index) + { + strg->local_pot[i] += + (this->get_electrostatic_potential_by_indices(i, strg->dependent_cell_index)) * + charge_diff; + } + } + strg->cell_charge[strg->dependent_cell_index] = sidb_charge_state::NEGATIVE; + } } + else if ((loc_pot_cell + strg->phys_params.mu_plus()) > -physical_constants::POP_STABILITY_ERR) + { + if (strg->cell_charge[strg->dependent_cell_index] != sidb_charge_state::POSITIVE) + { + const auto charge_diff = (-charge_state_to_sign(strg->cell_charge[strg->dependent_cell_index]) + 1); + for (uint64_t i = 0u; i < strg->pot_mat.size(); ++i) + { + if (i != strg->dependent_cell_index) + { + strg->local_pot[i] += + (this->get_electrostatic_potential_by_indices(i, strg->dependent_cell_index)) * + charge_diff; + } + } + strg->cell_charge[strg->dependent_cell_index] = sidb_charge_state::POSITIVE; + } + } + + else + { + if (strg->cell_charge[strg->dependent_cell_index] != sidb_charge_state::NEUTRAL) + { + const auto charge_diff = (-charge_state_to_sign(strg->cell_charge[strg->dependent_cell_index])); + for (uint64_t i = 0u; i < strg->pot_mat.size(); ++i) + { + if (i != strg->dependent_cell_index) + { + strg->local_pot[i] += + (this->get_electrostatic_potential_by_indices(i, strg->dependent_cell_index)) * + charge_diff; + } + } + strg->cell_charge[strg->dependent_cell_index] = sidb_charge_state::NEUTRAL; + } + } + } + } + /** + * This function returns the charge index of the sublayout (cells that can be positively charged). + * + * @returns The charge distribution index of the sublayout. + */ + [[nodiscard]] uint64_t get_charge_index_of_sub_layout() const noexcept + { + return strg->charge_index_sublayout; + } + /** + * This function changes the current charge distribution based on two given Gray codes (Important: The two Gray + * codes should only differ by one bit) + * + * @param new_gray_code Gray code as uint64_t of the new charge distribution. + * @param old_gray_code Gray code as uint64_t of the previous charge distribution layout. + */ + void charge_index_gray_code_to_charge_distribution(uint64_t new_gray_code, uint64_t old_gray_code) noexcept + { + strg->cell_history_gray_code = {}; + + const std::bitset<64> r_new(new_gray_code); + const std::bitset<64> r_old(old_gray_code); + const std::bitset<64> diff = r_new ^ r_old; + + uint64_t index_changed = 0; + + // Check if there are differing bits (if 'diff' is not equal to 0). + if (diff != 0) + { + // Find the bit position of the first difference. This position then describes the cell index of the SiDB + // that has changed its charge state. + while (index_changed < diff.size() && !diff.test(index_changed)) + { + index_changed++; + } + + const auto sign_old = int8_t{-1} * static_cast(r_old[index_changed]); + const auto sign_new = int8_t{-1} * static_cast(r_new[index_changed]); + + if (index_changed < strg->dependent_cell_index) + { + strg->cell_history_gray_code.first = static_cast(index_changed); + strg->cell_history_gray_code.second = sign_old; + this->assign_charge_state_by_cell_index(index_changed, sign_to_charge_state(sign_new), false); + } + else + { + strg->cell_history_gray_code.first = static_cast(index_changed) + 1; + strg->cell_history_gray_code.second = sign_old; + this->assign_charge_state_by_cell_index(index_changed + 1, sign_to_charge_state(sign_new), false); + } + } + else + { + strg->cell_history_gray_code.first = -1; + strg->cell_history_gray_code.second = 0; + } + } + /** + * The charge index of the sublayout is increased by one and the charge distribution is updated correspondingly. + * + * @param dependent_cell dependent_cell_mode::FIXED if the state of the dependent cell should not change, + * dependent_cell_mode::VARIABLE if it should. + * @param energy_calculation_mode energy_calculation::UPDATE_ENERGY if the electrostatic potential energy should be + * updated, energy_calculation::KEEP_ENERGY otherwise. + * @param history_mode charge_distribution_history::NEGLECT if the information (local electrostatic energy) of the + * previous charge distribution is used to make the update more efficient, charge_distribution_history::CONSIDER + * otherwise. + * @param engine exhaustive_algorithm::EXGS if `ExGS``should be used, exhaustive_algorithm::QUICKEXACT for + * `QuickExact`. + */ + void increase_charge_index_of_sub_layout_by_one( + const dependent_cell_mode& dependent_cell_fixed = dependent_cell_mode::FIXED, + const energy_calculation& recompute_system_energy = energy_calculation::UPDATE_ENERGY, + const charge_distribution_history& consider_history = charge_distribution_history::NEGLECT, + const exhaustive_algorithm& engine = exhaustive_algorithm::QUICKEXACT) noexcept + { + if (strg->charge_index_sublayout < strg->max_charge_index_sulayout) + { + strg->charge_index_sublayout += 1; + this->index_to_charge_distribution(engine); + this->update_after_charge_change(dependent_cell_fixed, recompute_system_energy, consider_history); + } + } + /** + * The charge index is assigned by a Gray code number in decimal. + * + * @param current_gray_code Gray code in decimal representing the new charge distribution. + * @param previous_gray_code Gray code in decimal representing the old charge distribution. + * @param dependent_cell dependent_cell_mode::FIXED if the state of the dependent cell should not change, + * dependent_cell_mode::VARIABLE if it should. + * @param energy_calculation_mode energy_calculation::UPDATE_ENERGY if the electrostatic potential energy should be + * updated, energy_calculation::KEEP_ENERGY otherwise. + * @param history_mode charge_distribution_history::NEGLECT if the information (local electrostatic energy) of the + * previous charge distribution is used to make the update more efficient, charge_distribution_history::CONSIDER + * otherwise. + */ + void assign_charge_index_by_gray_code( + const uint64_t current_gray_code, const uint64_t previous_gray_code, + const dependent_cell_mode& dependent_cell = dependent_cell_mode::FIXED, + const energy_calculation& energy_calc_mode = energy_calculation::UPDATE_ENERGY, + const charge_distribution_history& history_mode = charge_distribution_history::NEGLECT) noexcept + { + if (current_gray_code <= strg->max_charge_index) + { + this->assign_charge_index_by_two_gray_codes(current_gray_code, previous_gray_code); + this->update_after_charge_change(dependent_cell, energy_calc_mode, history_mode); } } + /** + * Resets the charge index of the sublayout (cells of the layout that can also be positively charged). + */ + void reset_charge_index_sub_layout() noexcept + { + strg->charge_index_sublayout = 0; + this->index_to_charge_distribution(exhaustive_algorithm::QUICKEXACT); + this->update_after_charge_change(dependent_cell_mode::VARIABLE, energy_calculation::KEEP_OLD_ENERGY_VALUE, + charge_distribution_history::CONSIDER); + } + /** + * Returns the maximum index of the sublayout (cells that can be positively charged). + * + * @returns The maximal possible charge distribution index of the sublayout. + */ + [[nodiscard]] uint64_t get_max_charge_index_sub_layout() const noexcept + { + return strg->max_charge_index_sulayout; + } + /** + * Assign a given charge index to the charge distribution layout. This function should be used if new and old charge + * index are given as Gray code to provide high performance. + * + * @param gray_code charge index (as Gray code in decimal) of the new charge distribution. + * @param gray_code_old charge index (as Gray code in decimal) of the old charge distribution. + */ + void assign_charge_index_by_two_gray_codes(const uint64_t gray_code, const uint64_t gray_code_old) noexcept + { + strg->charge_index_and_base.first = gray_code; + this->charge_index_gray_code_to_charge_distribution(gray_code, gray_code_old); + } + /** + * This function returns all SiDBs of the layout. + * + * @return Vector with all cells. + */ + std::vector get_sidb_order() noexcept + { + return strg->sidb_order; + } + /** + * This function can be used to add an SiDB to the layout. The SiDB is only added to the cell_charge and the + * sidb_order vector. + * + * @param cell Cell which is added to the layout. + * @param charge Charge state of the added cell. + */ + void add_sidb(const typename Lyt::cell& cell, const sidb_charge_state& charge) noexcept + { + strg->cell_charge.push_back(charge); + strg->sidb_order.push_back(cell); + } private: storage strg; @@ -850,6 +1820,7 @@ class charge_distribution_surface : public Lyt strg->sidb_order.reserve(this->num_cells()); strg->cell_charge.reserve(this->num_cells()); this->foreach_cell([this](const auto& c1) { strg->sidb_order.push_back(c1); }); + std::sort(strg->sidb_order.begin(), strg->sidb_order.end()); this->foreach_cell([this, &cs](const auto&) { strg->cell_charge.push_back(cs); }); assert((((this->num_cells() < 41) && (strg->phys_params.base == 3)) || @@ -859,12 +1830,63 @@ class charge_distribution_surface : public Lyt this->charge_distribution_to_index(); this->initialize_nm_distance_matrix(); this->initialize_potential_matrix(); - strg->max_charge_index = - static_cast(std::pow(static_cast(strg->phys_params.base), this->num_cells()) - 1); + if (!strg->dependent_cell.is_dead()) + { + strg->max_charge_index = + static_cast(std::pow(static_cast(strg->phys_params.base), this->num_cells() - 1) - 1); + } + else + { + strg->max_charge_index = + static_cast(std::pow(static_cast(strg->phys_params.base), this->num_cells()) - 1); + } + strg->dependent_cell_index = static_cast(cell_to_index(strg->dependent_cell)); this->update_local_potential(); this->recompute_system_energy(); this->validity_check(); }; + /** + * This function is used when three state simulations are required (i.e., is_three_state_simulation_required = true) + * to set the base number to three. However, it is distinguished between the cells that can be positively charged an + * the ones that cannot. + * + * @note is_three_state_simulation_required() has to be executed first. + */ + void assign_base_number_to_three() noexcept + { + strg->phys_params.base = 3; + strg->charge_index_and_base.second = 2; + if (!strg->dependent_cell.is_dead()) + { + if (!strg->three_state_cells.empty()) + { + if (std::find(strg->three_state_cells.cbegin(), strg->three_state_cells.cend(), strg->dependent_cell) != + strg->three_state_cells.cend()) + { + strg->max_charge_index = + static_cast(std::pow(2, this->num_cells() - strg->three_state_cells.size()) - 1); + strg->max_charge_index_sulayout = + static_cast(std::pow(3, strg->three_state_cells.size() - 1) - 1); + } + else + { + strg->max_charge_index = + static_cast(std::pow(2, this->num_cells() - 1 - strg->three_state_cells.size()) - 1); + strg->max_charge_index_sulayout = + static_cast(std::pow(3, strg->three_state_cells.size()) - 1); + } + } + } + else + { + strg->max_charge_index = static_cast(std::pow(3, this->num_cells()) - 1); + strg->max_charge_index_sulayout = static_cast(std::pow(3, strg->three_state_cells.size()) - 1); + } + if (strg->max_charge_index == 0) + { + this->assign_charge_index(0); + } + } /** * Initializes the distance matrix between all the cells of the layout. @@ -899,17 +1921,227 @@ class charge_distribution_surface : public Lyt } } } + /** + * The stored unique index is converted to a charge distribution. + * + * @param engine exhaustive_algorithm::EXGS if `ExGS``should be used, exhaustive_algorithm::QUICKEXACT for + * `QuickExact`. + */ + void index_to_charge_distribution(const exhaustive_algorithm& engine = exhaustive_algorithm::EXGS) noexcept + { + // This scope is executed if the function is used by `quickexact`. + if (engine == exhaustive_algorithm::QUICKEXACT) + { + // Cell_history collects the cells (SiDBs) that have changed their charge state. + strg->cell_history = {}; + strg->cell_history.reserve(this->num_cells()); + + // If the charge index is set to zero, first, all SiDBs that are not among the "positive candidates" (sub + // layout) are updated. + if (strg->charge_index_and_base.first == 0) + { + for (const auto& cell : strg->sidb_order_without_three_state_cells) + { + if (this->get_charge_state(cell) != sidb_charge_state::NEGATIVE && cell != strg->dependent_cell) + { + strg->cell_history.emplace_back(cell_to_index(cell), + charge_state_to_sign(get_charge_state(cell))); + this->assign_charge_state(cell, sidb_charge_state::NEGATIVE, false); + } + } + } + // If the charge index of the sublayout is zero, the charge states are updated. + if (strg->charge_index_sublayout == 0) + { + for (const auto& cell : strg->three_state_cells) + { + if (this->get_charge_state(cell) != sidb_charge_state::NEGATIVE && cell != strg->dependent_cell) + { + strg->cell_history.emplace_back(cell_to_index(cell), + charge_state_to_sign(get_charge_state(cell))); + this->assign_charge_state(cell, sidb_charge_state::NEGATIVE, false); + } + } + } + + // Get the index of the dependent-cell. If it is not part of the sublayout, -1 is returned. + const auto dependent_cell_index = positive_cell_to_index(strg->dependent_cell); + + auto charge_quot_positive = strg->charge_index_sublayout; + const auto base_positive = 3; + auto counter = strg->three_state_cells.size() - 1; + // Firstly, the charge distribution of the sublayout (i.e., collection of SiDBs that can be positively + // charged) is updated. + while (charge_quot_positive > 0) + { + const auto charge_quot_int = static_cast(charge_quot_positive); + const auto base_int = static_cast(base_positive); + const int64_t quotient_int = charge_quot_int / base_int; + const int64_t remainder_int = charge_quot_int % base_int; + charge_quot_positive = static_cast(quotient_int); + + if (counter != dependent_cell_index) + { + const auto sign = sign_to_charge_state(static_cast(remainder_int - 1)); + if (const auto new_chargesign = this->get_charge_state_by_index( + static_cast(cell_to_index(index_to_three_state_cell(counter)))); + new_chargesign != sign) + { + strg->cell_history.emplace_back( + static_cast(cell_to_index(index_to_three_state_cell(counter))), + charge_state_to_sign(new_chargesign)); + this->assign_charge_state_by_cell_index( + static_cast(cell_to_index(index_to_three_state_cell(counter))), sign, false); + } + counter -= 1; + } + else + { + counter -= 1; + const auto sign = sign_to_charge_state(static_cast(remainder_int - 1)); + if (const auto old_chargesign = this->get_charge_state_by_index( + static_cast(cell_to_index(index_to_three_state_cell(counter)))); + old_chargesign != sign) + { + strg->cell_history.emplace_back( + static_cast(cell_to_index(index_to_three_state_cell(counter))), + charge_state_to_sign(old_chargesign)); + this->assign_charge_state_by_cell_index( + static_cast(cell_to_index(index_to_three_state_cell(counter))), sign, false); + } + counter -= 1; + } + } + + const auto dependent_cell_index_negative = two_state_cell_to_index(strg->dependent_cell); + auto charge_quot = strg->charge_index_and_base.first; + const auto base = strg->charge_index_and_base.second; + auto counter_negative = strg->sidb_order_without_three_state_cells.size() - 1; + + // Secondly, the charge distribution of the layout (only SiDBs which can be either neutrally or negatively + // charged) is updated. + while (charge_quot > 0) + { + const auto charge_quot_int = static_cast(charge_quot); + const auto base_int = static_cast(base); + const int64_t quotient_int = charge_quot_int / base_int; + const int64_t remainder_int = charge_quot_int % base_int; + charge_quot = static_cast(quotient_int); + // If the current position is not the dependent-cell position, the charge state is updated. + if (counter_negative != dependent_cell_index_negative) + { + const auto sign = sign_to_charge_state(static_cast(remainder_int - 1)); + if (const auto new_chargesign = this->get_charge_state_by_index(static_cast( + cell_to_index(index_to_two_state_cell(static_cast(counter_negative))))); + new_chargesign != sign) + { + strg->cell_history.emplace_back(static_cast(cell_to_index(index_to_two_state_cell( + static_cast(counter_negative)))), + charge_state_to_sign(new_chargesign)); + this->assign_charge_state_by_cell_index( + static_cast( + cell_to_index(index_to_two_state_cell(static_cast(counter_negative)))), + sign, false); + } + counter_negative -= 1; + } + // If the current position is the dependent cell position, first the counter_negative is decremented by + // one to get to the next cell position to update its charge state. + else + { + counter_negative -= 1; + const auto sign = sign_to_charge_state(static_cast(remainder_int - 1)); + if (const auto old_chargesign = this->get_charge_state_by_index( + cell_to_index(index_to_two_state_cell(static_cast(counter_negative)))); + old_chargesign != sign) + { + strg->cell_history.emplace_back(static_cast(cell_to_index(index_to_two_state_cell( + static_cast(counter_negative)))), + charge_state_to_sign(old_chargesign)); + this->assign_charge_state_by_cell_index( + cell_to_index(index_to_two_state_cell(static_cast(counter_negative))), sign, + false); + } + counter_negative -= 1; + } + } + } + else + { + auto charge_quot = strg->charge_index_and_base.first; + const auto base = strg->charge_index_and_base.second; + const auto num_charges = this->num_cells(); + auto counter = num_charges - 1; + const auto dependent_cell_index = cell_to_index(strg->dependent_cell); + + // A charge index of zero corresponds to a layout with all SiDBs set to negative. + if (charge_quot == 0) + { + this->assign_all_charge_states(sidb_charge_state::NEGATIVE); + } + else + { + while (charge_quot > 0) + { + const auto charge_quot_int = static_cast(charge_quot); + const auto base_int = static_cast(base); + const int64_t quotient_int = charge_quot_int / base_int; + const int64_t remainder_int = charge_quot_int % base_int; + charge_quot = static_cast(quotient_int); + // Dependent-SiDB is skipped since its charge state is not changed based on the charge index. + + if (dependent_cell_index >= 0) + { + if (counter != dependent_cell_index) + { + const auto sign = sign_to_charge_state(static_cast(remainder_int - 1)); + this->assign_charge_state_by_cell_index(counter, sign, false); + counter -= 1; + } + // If the counter is at the dependent-cell location, it is reduced by one to get to the next + // cell position. + else + { + counter -= 1; + const auto sign = sign_to_charge_state(static_cast(remainder_int - 1)); + // The charge state is only changed (i.e., the function assign_charge_state_by_cell_index is + // called), if the nw charge state differs to the previous one. Only then will the cell be + // added to the charge_distribution_history. + this->assign_charge_state_by_cell_index(counter, sign, false); + counter -= 1; + } + } + else + { + const auto sign = sign_to_charge_state(static_cast(remainder_int - 1)); + this->assign_charge_state_by_cell_index(counter, sign, false); + counter -= 1; + } + } + } + } + } }; template charge_distribution_surface(const T&) -> charge_distribution_surface; +template +charge_distribution_surface(const T&, const sidb_simulation_parameters&) -> charge_distribution_surface; + template charge_distribution_surface(const T&, const sidb_simulation_parameters&, const sidb_charge_state& cs) -> charge_distribution_surface; template -charge_distribution_surface(const T&, const sidb_simulation_parameters&) -> charge_distribution_surface; +charge_distribution_surface(const T&, const sidb_simulation_parameters&, const sidb_charge_state& cs, + const typename T::cell& variable_cells) -> charge_distribution_surface; + +template +charge_distribution_surface(const T&, const sidb_simulation_parameters&, const sidb_charge_state& cs, + const typename T::cell& variable_cells, + const std::unordered_map& external_pot) + -> charge_distribution_surface; } // namespace fiction diff --git a/include/fiction/types.hpp b/include/fiction/types.hpp index 8575d7607..c1e5415fe 100644 --- a/include/fiction/types.hpp +++ b/include/fiction/types.hpp @@ -15,6 +15,7 @@ #include "fiction/layouts/tile_based_layout.hpp" #include "fiction/networks/technology_network.hpp" #include "fiction/technology/cell_technologies.hpp" +#include "fiction/technology/sidb_surface.hpp" #include #include @@ -154,6 +155,9 @@ using sidb_cell_clk_lyt_ptr = std::shared_ptr; using sidb_cell_clk_lyt_siqad = cell_level_layout>>; using sidb_cell_clk_lyt_siqad_ptr = std::shared_ptr; +using sidb_defect_cell_clk_lyt_siqad = sidb_surface; +using sidb_defect_cell_clk_lyt_siqad_ptr = std::shared_ptr; + using cell_layout_t = std::variant; diff --git a/include/fiction/utils/execution_utils.hpp b/include/fiction/utils/execution_utils.hpp index a723c43af..9550fada7 100644 --- a/include/fiction/utils/execution_utils.hpp +++ b/include/fiction/utils/execution_utils.hpp @@ -46,14 +46,14 @@ /** * Parallel execution policy for STL algorithms. * - * @note This macro automatically detetcs whether the C++ library supports execution policies and whether the compiler + * @note This macro automatically detects whether the C++ library supports execution policies and whether the compiler * is able to compile them. If not, the macro defaults to nothing. */ #define FICTION_EXECUTION_POLICY_PAR /** * Parallel unsequenced execution policy for STL algorithms. * - * @note This macro automatically detetcs whether the C++ library supports execution policies and whether the compiler + * @note This macro automatically detects whether the C++ library supports execution policies and whether the compiler * is able to compile them. If not, the macro defaults to nothing. */ #define FICTION_EXECUTION_POLICY_PAR_UNSEQ diff --git a/test/algorithms/simulation/sidb/can_positive_charges_occur.cpp b/test/algorithms/simulation/sidb/can_positive_charges_occur.cpp new file mode 100644 index 000000000..12155d147 --- /dev/null +++ b/test/algorithms/simulation/sidb/can_positive_charges_occur.cpp @@ -0,0 +1,73 @@ +// +// Created by Jan Drewniok on 12.08.23. +// + +#include + +#include +#include + +using namespace fiction; + +TEMPLATE_TEST_CASE("One BDL pair with one perturber", "[can_positive_charges_occur]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({4, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({6, 0, 0}, TestType::cell_type::NORMAL); + + SECTION("Default values") + { + const sidb_simulation_parameters params{2, -0.32}; + CHECK(!can_positive_charges_occur(lyt, params)); + } + + SECTION("epsilon = 1, lambda = 1") + { + const sidb_simulation_parameters params{2, -0.32, 1, 1}; + CHECK(can_positive_charges_occur(lyt, params)); + } + + SECTION("epsilon = 1, lambda = 10") + { + const sidb_simulation_parameters params{2, -0.32, 1, 10}; + CHECK(can_positive_charges_occur(lyt, params)); + } +} + +TEMPLATE_TEST_CASE("Y-shape SiDB OR gate with input 01", "[can_positive_charges_occur]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({6, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({8, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({12, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({14, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 5, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({10, 6, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 8, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({16, 1, 0}, TestType::cell_type::NORMAL); + + SECTION("Default values") + { + const sidb_simulation_parameters params{2, -0.32}; + CHECK(!can_positive_charges_occur(lyt, params)); + } + + SECTION("epsilon = 1, lambda = 1") + { + const sidb_simulation_parameters params{2, -0.32, 1, 1}; + CHECK(can_positive_charges_occur(lyt, params)); + } + + SECTION("epsilon = 1, lambda = 10") + { + const sidb_simulation_parameters params{2, -0.32, 1, 10}; + CHECK(can_positive_charges_occur(lyt, params)); + } +} diff --git a/test/algorithms/simulation/sidb/critical_temperature.cpp b/test/algorithms/simulation/sidb/critical_temperature.cpp index 0c4ae9e5c..ecbb6457c 100644 --- a/test/algorithms/simulation/sidb/critical_temperature.cpp +++ b/test/algorithms/simulation/sidb/critical_temperature.cpp @@ -42,6 +42,7 @@ TEMPLATE_TEST_CASE( TestType lyt{{10, 10}}; lyt.assign_cell_type({0, 0}, TestType::cell_type::NORMAL); + // gate-based simulation critical_temperature_stats criticalstats{}; const critical_temperature_params params{simulation_engine::EXACT, critical_temperature_mode::GATE_BASED_SIMULATION, @@ -54,10 +55,18 @@ TEMPLATE_TEST_CASE( CHECK(criticalstats.num_valid_lyt == 1); CHECK(criticalstats.critical_temperature == 350); - critical_temperature_stats criticalstats_new{}; - critical_temperature(lyt, params, &criticalstats_new); - CHECK(criticalstats_new.num_valid_lyt == 1); - CHECK(criticalstats_new.critical_temperature == 350); + // non-gate-based simulation + critical_temperature_stats criticalstats_non_gate_based{}; + const critical_temperature_params params_non_gate_based{simulation_engine::EXACT, + critical_temperature_mode::NON_GATE_BASED_SIMULATION, + quicksim_params{sidb_simulation_parameters{2, -0.32}}, + 0.99, + 350, + create_or_tt(), + 2}; + critical_temperature(lyt, params, &criticalstats_non_gate_based); + CHECK(criticalstats_non_gate_based.num_valid_lyt == 1); + CHECK(criticalstats_non_gate_based.critical_temperature == 350); } SECTION("several SiDBs placed") @@ -199,7 +208,7 @@ TEMPLATE_TEST_CASE( critical_temperature_mode::NON_GATE_BASED_SIMULATION, quicksim_params{sidb_simulation_parameters{2, -0.15}}, 0.99, 350}; critical_temperature(lyt, params, &criticalstats); - CHECK(criticalstats.algorithm_name == "exgs"); + CHECK(criticalstats.algorithm_name == "QuickExact"); CHECK(criticalstats.critical_temperature < 200); CHECK(criticalstats.critical_temperature > 0); } @@ -229,7 +238,37 @@ TEMPLATE_TEST_CASE( critical_temperature_mode::NON_GATE_BASED_SIMULATION, quicksim_params{sidb_simulation_parameters{2, -0.15}}, 0.99, 350}; critical_temperature(lyt, params, &criticalstats); - CHECK(criticalstats.algorithm_name == "quicksim"); + CHECK(criticalstats.algorithm_name == "QuickSim"); + CHECK(criticalstats.critical_temperature < 200); + CHECK(criticalstats.critical_temperature > 0); + } + + SECTION("small fo2 for testing") + { + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 0, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({0, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({0, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({-2, 5, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({-3, 6, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({-3, 8, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({2, 5, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 6, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 8, 0}, TestType::cell_type::NORMAL); + + critical_temperature_stats criticalstats{}; + const critical_temperature_params params{simulation_engine::APPROXIMATE, + critical_temperature_mode::GATE_BASED_SIMULATION, + quicksim_params{sidb_simulation_parameters{2, -0.35}}, + 0.99, + 350, + create_fan_out_tt(), + 1}; + critical_temperature(lyt, params, &criticalstats); + CHECK(criticalstats.algorithm_name == "QuickSim"); CHECK(criticalstats.critical_temperature < 200); CHECK(criticalstats.critical_temperature > 0); } diff --git a/test/algorithms/simulation/sidb/exhaustive_ground_state_simulation.cpp b/test/algorithms/simulation/sidb/exhaustive_ground_state_simulation.cpp index f3712166e..0d00c31a6 100644 --- a/test/algorithms/simulation/sidb/exhaustive_ground_state_simulation.cpp +++ b/test/algorithms/simulation/sidb/exhaustive_ground_state_simulation.cpp @@ -43,6 +43,21 @@ TEMPLATE_TEST_CASE("Single SiDB ExGS simulation", "[ExGS]", CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::NEGATIVE); } +TEMPLATE_TEST_CASE("ExGS simulation of a one BDL pair with one perturber", "[ExGS]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({4, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({6, 0, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{2, -0.32}; + + const auto simulation_results = exhaustive_ground_state_simulation(lyt, params); + CHECK(simulation_results.charge_distributions.size() == 1); +} + TEMPLATE_TEST_CASE("ExGS simulation of a two-pair BDL wire with one perturber", "[ExGS]", (cell_level_layout>>)) { diff --git a/test/algorithms/simulation/sidb/maximum_defect_influence_position_and_distance.cpp b/test/algorithms/simulation/sidb/maximum_defect_influence_position_and_distance.cpp new file mode 100644 index 000000000..aa6fcd7be --- /dev/null +++ b/test/algorithms/simulation/sidb/maximum_defect_influence_position_and_distance.cpp @@ -0,0 +1,116 @@ +// +// Created by Jan Drewniok on 26.06.23. +// + +#include +#include + +#include +#include +#include +#include + +using namespace fiction; + +TEST_CASE("Test influence distance function", "[maximum_defect_influence_position_and_distance]") +{ + SECTION("empty layout") + { + const sidb_defect defect{sidb_defect_type::UNKNOWN, -1, 10.6, 5.9}; + const maximum_defect_influence_distance_params sim_params{defect, sidb_simulation_parameters{}}; + const sidb_cell_clk_lyt_siqad lyt{}; + + const auto [defect_pos, distance] = maximum_defect_influence_position_and_distance(lyt, sim_params); + CHECK(distance == 0); + CHECK(defect_pos == coordinate()); + } + + SECTION("layout with one SiDB") + { + const sidb_defect defect{sidb_defect_type::UNKNOWN, -1, sidb_simulation_parameters{}.epsilon_r, + sidb_simulation_parameters{}.lambda_tf}; + const maximum_defect_influence_distance_params sim_params{defect, sidb_simulation_parameters{}, {50, 6}}; + + sidb_cell_clk_lyt_siqad lyt{}; + lyt.assign_cell_type({0, 0, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + + const auto [defect_pos, distance] = maximum_defect_influence_position_and_distance(lyt, sim_params); + CHECK_THAT(round_to_n_decimal_places(distance, 6), + Catch::Matchers::WithinAbs(0.665060, physical_constants::POP_STABILITY_ERR)); + CHECK((((defect_pos.x == -1) && (defect_pos.y == -1) && (defect_pos.z == 1)) || + ((defect_pos.x == 1) && (defect_pos.y == -1) && (defect_pos.z == 1)))); + } + + SECTION("layout with one SiDB, negative defect, smaller lambda_tf") + { + const sidb_defect defect{sidb_defect_type::UNKNOWN, -1, sidb_simulation_parameters{}.epsilon_r, 1}; + const maximum_defect_influence_distance_params sim_params{defect, sidb_simulation_parameters{}}; + sidb_cell_clk_lyt_siqad lyt{}; + lyt.assign_cell_type({0, 0, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + const auto [defect_pos, distance] = maximum_defect_influence_position_and_distance(lyt, sim_params); + CHECK_THAT(round_to_n_decimal_places(distance, 4) - + round_to_n_decimal_places(sidb_nanometer_distance(lyt, {0, 0, 0}, {-1, 0, 1}), 4), + Catch::Matchers::WithinAbs(0.0, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("layout with one SiDB, negative defect, large lambda_tf") + { + const sidb_defect defect{sidb_defect_type::UNKNOWN, -1, sidb_simulation_parameters{}.epsilon_r, 20}; + const maximum_defect_influence_distance_params sim_params{defect, sidb_simulation_parameters{}, {2, 2}}; + sidb_cell_clk_lyt_siqad lyt{}; + lyt.assign_cell_type({0, 0, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + const auto [defect_pos, distance] = maximum_defect_influence_position_and_distance(lyt, sim_params); + CHECK_THAT(round_to_n_decimal_places(distance, 4) - + round_to_n_decimal_places(sidb_nanometer_distance(lyt, {0, 0, 0}, {0, 1, 0}), 4), + Catch::Matchers::WithinAbs(0.0, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("layout with one pertuber and one DB pair, negative defect") + { + const sidb_defect defect{sidb_defect_type::UNKNOWN, -1, sidb_simulation_parameters{}.epsilon_r, + sidb_simulation_parameters{}.lambda_tf}; + const maximum_defect_influence_distance_params sim_params{defect, sidb_simulation_parameters{}}; + sidb_cell_clk_lyt_siqad lyt{}; + lyt.assign_cell_type({0, 0, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + lyt.assign_cell_type({4, 0, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + lyt.assign_cell_type({6, 0, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + + const auto [defect_pos, distance] = maximum_defect_influence_position_and_distance(lyt, sim_params); + CHECK_THAT(round_to_n_decimal_places(distance, 4) - + round_to_n_decimal_places(sidb_nanometer_distance(lyt, {6, 0, 0}, {10, 0, 0}), 4), + Catch::Matchers::WithinAbs(0.0, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("QuickExact simulation of a Y-shape SiDB OR gate with input 01") + { + const sidb_defect defect{sidb_defect_type::UNKNOWN, -1, sidb_simulation_parameters{}.epsilon_r, + sidb_simulation_parameters{}.lambda_tf}; + const maximum_defect_influence_distance_params sim_params{defect, sidb_simulation_parameters{}}; + sidb_cell_clk_lyt_siqad lyt{}; + + lyt.assign_cell_type({10, 0, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + lyt.assign_cell_type({0, 1, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + lyt.assign_cell_type({8, 1, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + + lyt.assign_cell_type({2, 2, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + lyt.assign_cell_type({6, 2, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + lyt.assign_cell_type({4, 4, 0}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + lyt.assign_cell_type({4, 5, 1}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + lyt.assign_cell_type({4, 7, 1}, sidb_cell_clk_lyt_siqad::cell_type::NORMAL); + + const auto [defect_pos, distance] = maximum_defect_influence_position_and_distance(lyt, sim_params); + CHECK(defect_pos.x == 12); + CHECK(defect_pos.y == 4); + CHECK(defect_pos.z == 1); + + // number of threads given by the hardware + const sidb_defect high_screening{sidb_defect_type::UNKNOWN, -1, sidb_simulation_parameters{}.epsilon_r, 1}; + const maximum_defect_influence_distance_params sim_params_high_screening{high_screening, + sidb_simulation_parameters{}}; + + const auto [defect_pos_high_screeing, distance_high_screeing] = + maximum_defect_influence_position_and_distance(lyt, sim_params_high_screening); + + CHECK(distance_high_screeing < distance); + } +} diff --git a/test/algorithms/simulation/sidb/quickexact.cpp b/test/algorithms/simulation/sidb/quickexact.cpp new file mode 100644 index 000000000..876d1da0c --- /dev/null +++ b/test/algorithms/simulation/sidb/quickexact.cpp @@ -0,0 +1,1319 @@ +// +// Created by Jan Drewniok on 18.12.22. + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace fiction; + +TEMPLATE_TEST_CASE("Empty layout QuickExact simulation", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + const quickexact_params params{sidb_simulation_parameters{2, -0.32}}; + + const auto simulation_results = quickexact(lyt, params); + + CHECK(simulation_results.charge_distributions.empty()); + CHECK(simulation_results.additional_simulation_parameters.empty()); + CHECK(simulation_results.algorithm_name == "QuickExact"); + CHECK(simulation_results.additional_simulation_parameters.empty()); +} + +TEMPLATE_TEST_CASE("Single SiDB QuickExact simulation", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{2, -0.32}}; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::NEGATIVE); +} + +TEMPLATE_TEST_CASE( + "Single SiDB QuickExact simulation with one negatively charge defect (default initialization) in proximity", + "[quickexact]", + (sidb_surface>>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + const quickexact_params params{sidb_simulation_parameters{2, -0.25}}; + lyt.assign_sidb_defect({1, 2, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -1, params.physical_parameters.epsilon_r, + params.physical_parameters.lambda_tf}); + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::NEUTRAL); +} + +TEMPLATE_TEST_CASE( + "Single SiDB QuickExact simulation with one negatively charge defect (changed lambda_tf) in proximity", + "[quickexact]", + (sidb_surface>>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{2, -0.25}}; + + lyt.assign_sidb_defect({1, 2, 0}, + sidb_defect{sidb_defect_type::UNKNOWN, -1, params.physical_parameters.epsilon_r, 2}); + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::NEGATIVE); +} + +TEMPLATE_TEST_CASE( + "Single SiDB QuickExact simulation with one negatively charge defect (changed epsilon_r) in proximity", + "[quickexact]", + (sidb_surface>>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{2, -0.25}}; + + lyt.assign_sidb_defect({1, 6, 0}, + sidb_defect{sidb_defect_type::UNKNOWN, -1, 0.3, params.physical_parameters.lambda_tf}); + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().num_defects() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::POSITIVE); +} + +TEMPLATE_TEST_CASE( + "four SiDBs QuickExact simulation with one negatively charge defect (changed epsilon_r) in proximity", + "[quickexact]", + (sidb_surface>>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({-2, 0, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({2, 0, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({0, 1, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({2, 1, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{2, -0.15}}; + + lyt.assign_sidb_defect({0, 0, 1}, sidb_defect{sidb_defect_type::UNKNOWN, -1, params.physical_parameters.epsilon_r, + params.physical_parameters.lambda_tf}); + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().num_defects() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::NEUTRAL); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(1) == sidb_charge_state::NEUTRAL); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(2) == sidb_charge_state::NEUTRAL); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(3) == sidb_charge_state::NEUTRAL); +} + +TEMPLATE_TEST_CASE("Single SiDB QuickExact simulation with one highly negatively charge defect in proximity", + "[quickexact]", + (sidb_surface>>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.1}}; + + lyt.assign_sidb_defect({1, 2, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -10, params.physical_parameters.epsilon_r, + params.physical_parameters.lambda_tf}); + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().num_defects() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::POSITIVE); +} + +TEMPLATE_TEST_CASE( + "Single SiDB QuickExact simulation with one highly negatively charge defect in proximity but with high screening", + "[quickexact]", + (sidb_surface>>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{2, -0.1}}; + + lyt.assign_sidb_defect({1, 2, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -10, params.physical_parameters.epsilon_r, + params.physical_parameters.lambda_tf * 10E-5}); + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::NEGATIVE); +} + +TEMPLATE_TEST_CASE( + "Single SiDB QuickExact simulation with two highly negatively and oppositely charged defects in proximity", + "[quickexact]", + (sidb_surface>>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{2, -0.1}}; + + lyt.assign_sidb_defect({2, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -10, params.physical_parameters.epsilon_r, + params.physical_parameters.lambda_tf}); + lyt.assign_sidb_defect({-2, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, 10, params.physical_parameters.epsilon_r, + params.physical_parameters.lambda_tf}); + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().num_defects() == 2); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::NEGATIVE); +} + +TEMPLATE_TEST_CASE("Single SiDB QuickExact simulation with local external potential", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + + quickexact_params params{sidb_simulation_parameters{2, -0.25}}; + + params.local_external_potential.insert({{0, 0, 0}, -0.5}); + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::NEUTRAL); +} + +TEMPLATE_TEST_CASE("Single SiDB QuickExact simulation with local external potential (high)", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + + quickexact_params params{sidb_simulation_parameters{3, -0.25}}; + + params.local_external_potential.insert({{{0, 0, 0}, -1}}); + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::POSITIVE); +} + +TEMPLATE_TEST_CASE("Single SiDB QuickExact simulation with global external potential", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + + quickexact_params params{sidb_simulation_parameters{2, -0.25}}; + params.global_potential = -0.26; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::NEUTRAL); +} + +TEMPLATE_TEST_CASE("Single SiDB QuickExact simulation with global external potential (high)", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + + quickexact_params params{sidb_simulation_parameters{3, -0.25}}; + params.global_potential = -1; + + const auto simulation_results = quickexact(lyt, params); + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::POSITIVE); +} + +TEMPLATE_TEST_CASE("Single SiDB QuickExact simulation with global external potential (high, positive)", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + + quickexact_params params{sidb_simulation_parameters{3, -0.25}}; + params.global_potential = 1; + + const auto simulation_results = quickexact(lyt, params); + REQUIRE(simulation_results.charge_distributions.size() == 1); + CHECK(simulation_results.charge_distributions.front().get_charge_state_by_index(0) == sidb_charge_state::NEGATIVE); +} + +TEMPLATE_TEST_CASE("QuickExact simulation of a BDL pair", "[quickexact]", + (cell_level_layout>>)) +{ + 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); + + const quickexact_params params{sidb_simulation_parameters{3, -0.25}}; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 2); + for (const auto& layouts : simulation_results.charge_distributions) + { + uint64_t counter_negative = 0; + uint64_t counter_neutral = 0; + for (uint64_t i = 0; i < 2; i++) + { + if (layouts.get_charge_state_by_index(i) == sidb_charge_state::NEGATIVE) + { + counter_negative += 1; + } + else + { + counter_neutral += 1; + } + } + CHECK(counter_neutral == 1); + CHECK(counter_negative == 1); + } +} + +TEMPLATE_TEST_CASE("QuickExact simulation of a two-pair BDL wire with one perturber", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 0, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({11, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({13, 0, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({17, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({19, 0, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.32}}; + + const auto simulation_results = quickexact(lyt, params); + auto size_before = simulation_results.charge_distributions.size(); + + const auto simulation_results_new = quickexact(lyt, params); + auto size_after = simulation_results_new.charge_distributions.size(); + + CHECK(size_before == 1); + CHECK(size_after == 1); + + REQUIRE(!simulation_results_new.charge_distributions.empty()); + + const auto& charge_lyt_first = simulation_results_new.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({5, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({7, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({11, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({13, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({17, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({19, 0, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(charge_lyt_first.get_system_energy(), + Catch::Matchers::WithinAbs(0.2460493219, physical_constants::POP_STABILITY_ERR)); +} + +TEMPLATE_TEST_CASE("QuickExact simulation of a one-pair BDL wire with two perturbers", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{50, 10}}; + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({15, 0, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{2, -0.32}; + + charge_distribution_surface charge_layout_kon{lyt, params}; + + charge_layout_kon.assign_charge_state({0, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout_kon.assign_charge_state({5, 0, 0}, sidb_charge_state::NEUTRAL); + charge_layout_kon.assign_charge_state({7, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout_kon.assign_charge_state({15, 0, 0}, sidb_charge_state::NEGATIVE); + + charge_layout_kon.update_after_charge_change(); + + const quickexact_params sim_params{sidb_simulation_parameters{3, -0.32}}; + + const auto simulation_results = quickexact(lyt, sim_params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({5, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({7, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({15, 0, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(charge_lyt_first.get_system_energy(), + Catch::Matchers::WithinAbs(0.1152677452, physical_constants::POP_STABILITY_ERR)); +} + +TEMPLATE_TEST_CASE("QuickExact simulation of a Y-shape SiDB arrangement", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({-11, -2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({-10, -1, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({-4, -1, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({-3, -2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({-7, 0, 1}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({-7, 1, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({-7, 3, 0}, TestType::cell_type::NORMAL); + + const quickexact_params sim_params{sidb_simulation_parameters{3, -0.32}}; + + const auto simulation_results = quickexact(lyt, sim_params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({-11, -2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({-10, -1, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({-3, -2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({-4, -1, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({-7, 0, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({-7, 1, 1}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({-7, 3, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(charge_lyt_first.get_system_energy(), + Catch::Matchers::WithinAbs(0.3191788254, physical_constants::POP_STABILITY_ERR)); +} + +TEMPLATE_TEST_CASE("QuickExact simulation of a Y-shape SiDB OR gate with input 01", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({6, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({8, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({12, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({14, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 5, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({10, 6, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 8, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({16, 1, 0}, TestType::cell_type::NORMAL); + + const quickexact_params sim_params{sidb_simulation_parameters{2, -0.28}}; + + const auto simulation_results = quickexact(lyt, sim_params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({6, 2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({12, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 8, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 6, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({16, 1, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 5, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({14, 2, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({8, 3, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({6, 2, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(charge_lyt_first.get_system_energy(), + Catch::Matchers::WithinAbs(0.4662582096, physical_constants::POP_STABILITY_ERR)); +} + +TEMPLATE_TEST_CASE( + "QuickExact simulation of a Y-shape SiDB OR gate with input 01 and local external potential at perturber", + "[quickexact]", (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({6, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({8, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({12, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({14, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 5, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({10, 6, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 8, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({16, 1, 0}, TestType::cell_type::NORMAL); + + quickexact_params params{sidb_simulation_parameters{3, -0.28}}; + params.local_external_potential.insert({{{6, 2, 0}, -0.5}}); + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({6, 2, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({12, 3, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({10, 8, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 6, 1}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({16, 1, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 5, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({14, 2, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({8, 3, 0}) == sidb_charge_state::NEGATIVE); +} + +TEMPLATE_TEST_CASE("QuickExact simulation of a Y-shape SiDB OR gate with input 01 and global external potential", + "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({6, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({8, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({12, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({14, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 5, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({10, 6, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 8, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({16, 1, 0}, TestType::cell_type::NORMAL); + + quickexact_params params{sidb_simulation_parameters{3, -0.28}}; + params.global_potential = -0.5; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({6, 2, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({12, 3, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({10, 8, 1}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({10, 6, 1}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({16, 1, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({10, 5, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({14, 2, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({8, 3, 0}) == sidb_charge_state::NEUTRAL); +} + +TEMPLATE_TEST_CASE("QuickExact simulation of a Y-shape SiDB OR gate with input 01 and global external potential (high)", + "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({6, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({8, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({12, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({14, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 5, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({10, 6, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 8, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({16, 1, 0}, TestType::cell_type::NORMAL); + + quickexact_params params{sidb_simulation_parameters{3, -0.28}}; + params.global_potential = -2; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({6, 2, 0}) == sidb_charge_state::POSITIVE); + CHECK(charge_lyt_first.get_charge_state({12, 3, 0}) == sidb_charge_state::POSITIVE); + CHECK(charge_lyt_first.get_charge_state({10, 8, 1}) == sidb_charge_state::POSITIVE); + CHECK(charge_lyt_first.get_charge_state({10, 6, 1}) == sidb_charge_state::POSITIVE); + CHECK(charge_lyt_first.get_charge_state({16, 1, 0}) == sidb_charge_state::POSITIVE); + CHECK(charge_lyt_first.get_charge_state({10, 5, 0}) == sidb_charge_state::POSITIVE); + CHECK(charge_lyt_first.get_charge_state({14, 2, 0}) == sidb_charge_state::POSITIVE); + CHECK(charge_lyt_first.get_charge_state({8, 3, 0}) == sidb_charge_state::POSITIVE); +} + +TEMPLATE_TEST_CASE("QuickExact simulation of four SiDBs (far away)", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({20, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({30, 0, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.28}}; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({20, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({30, 0, 0}) == sidb_charge_state::NEGATIVE); +} + +TEMPLATE_TEST_CASE("QuickExact simulation of four SiDBs (far away) with one negatively charged defects in proximity", + "[quickexact]", + (sidb_surface>>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({20, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({30, 0, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.28}}; + lyt.assign_sidb_defect({1, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -1, params.physical_parameters.epsilon_r, + params.physical_parameters.lambda_tf}); + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({0, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({10, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({20, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({30, 0, 0}) == sidb_charge_state::NEGATIVE); +} + +TEMPLATE_TEST_CASE("QuickExact simulation of four SiDBs (far away) with two negatively charged defects in proximity", + "[quickexact]", + (sidb_surface>>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({20, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({30, 0, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.28}}; + + lyt.assign_sidb_defect({1, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -1, params.physical_parameters.epsilon_r, + params.physical_parameters.lambda_tf}); + lyt.assign_sidb_defect({31, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -1, params.physical_parameters.epsilon_r, + params.physical_parameters.lambda_tf}); + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + CHECK(simulation_results.charge_distributions.front().num_defects() == 2); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({0, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({10, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({20, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({30, 0, 0}) == sidb_charge_state::NEUTRAL); +} + +TEMPLATE_TEST_CASE( + "QuickExact simulation of four SiDBs (far away) with one negatively and positively charged defect in proximity", + "[quickexact]", + (sidb_surface>>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({20, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({30, 0, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.28}}; + + lyt.assign_sidb_defect({1, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, 1, params.physical_parameters.epsilon_r, + params.physical_parameters.lambda_tf}); + lyt.assign_sidb_defect({31, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -1, params.physical_parameters.epsilon_r, + params.physical_parameters.lambda_tf}); + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + CHECK(simulation_results.charge_distributions.front().num_defects() == 2); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({20, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({30, 0, 0}) == sidb_charge_state::NEUTRAL); +} + +TEMPLATE_TEST_CASE("Seven randomly distributed DBs, test if dependent cell calculation works correctly", "[quickexact]", + (cell_level_layout>>)) +{ + 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); + + const quickexact_params params{sidb_simulation_parameters{3, -0.28}}; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({1, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({3, 3, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({4, 3, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({6, 3, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({7, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({6, 10, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({7, 10, 0}) == sidb_charge_state::NEGATIVE); +} + +TEMPLATE_TEST_CASE("three DBs next to each other", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({-1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({2, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 3, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{2, -0.25}}; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 3); + + auto energy_min = std::numeric_limits::max(); + for (const auto& layout : simulation_results.charge_distributions) + { + if (layout.get_system_energy() < energy_min) + { + energy_min = layout.get_system_energy(); + } + } + + for (const auto& layout : simulation_results.charge_distributions) + { + if (std::abs(layout.get_system_energy() - energy_min) < physical_constants::POP_STABILITY_ERR) + { + CHECK(layout.get_charge_state({1, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(layout.get_charge_state({1, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(layout.get_charge_state({2, 3, 0}) == sidb_charge_state::POSITIVE); + CHECK(layout.get_charge_state({3, 3, 0}) == sidb_charge_state::NEGATIVE); + } + } +} + +TEMPLATE_TEST_CASE("three DBs next to each other, small mu-", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({-1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({2, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 3, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{2, -0.8}}; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() > 0); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + CHECK(charge_lyt_first.get_charge_state({1, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({1, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({2, 3, 0}) == sidb_charge_state::POSITIVE); + CHECK(charge_lyt_first.get_charge_state({3, 3, 0}) == sidb_charge_state::NEGATIVE); +} + +TEMPLATE_TEST_CASE("four DBs next to each other, small mu-", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({2, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 3, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.25}}; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 2); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + CHECK_THAT(charge_lyt_first.get_system_energy(), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); +} + +TEMPLATE_TEST_CASE("seven DBs next to each other, small mu-", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({2, 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({5, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({6, 3, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.25}}; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(simulation_results.charge_distributions.size() == 3); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + CHECK(charge_lyt_first.get_system_energy() < 0.08); + CHECK(charge_lyt_first.get_system_energy() > -2.74); +} + +TEMPLATE_TEST_CASE("7 DBs next to each other (positively charged DBs occur)", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({2, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({4, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({6, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 0, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.25}}; + + const auto simulation_results = quickexact(lyt, params); + + CHECK(simulation_results.charge_distributions.size() == 5); +} + +TEMPLATE_TEST_CASE( + "7 DBs next to each other | only one physically valid charge distribution with only one neutrally charged DB", + "[quickexact]", (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({-6, 1, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({2, 4, 1}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({4, 6, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({8, 3, 1}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({-8, -3, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({-1, -1, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({0, 2, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.25}}; + + const auto simulation_results = quickexact(lyt, params); + + CHECK(simulation_results.charge_distributions.size() == 1); +} + +TEMPLATE_TEST_CASE("4 DBs next to each other (positively charged DBs occur)", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({2, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 0, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.1}}; + + const auto simulation_results = quickexact(lyt, params); + + CHECK(simulation_results.charge_distributions.size() == 2); +} + +TEMPLATE_TEST_CASE("5 DBs next to each other (positively charged DBs occur)", "[quickexact]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({-1, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({2, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({6, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 0, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.25}}; + + const auto simulation_results = quickexact(lyt, params); + + CHECK(lyt.num_cells() == 6); + + CHECK(simulation_results.charge_distributions.size() == 1); +} + +TEMPLATE_TEST_CASE("3 DBs next to each other (positively charged DBs occur)", "[quickexact]", + (cell_level_layout>>)) +{ + using sidb_layout = cell_level_layout>>; + + sidb_layout lyt{{20, 10}}; + + lyt.assign_cell_type({5, 0, 0}, sidb_layout::cell_type::NORMAL); + lyt.assign_cell_type({6, 0, 0}, sidb_layout::cell_type::NORMAL); + lyt.assign_cell_type({7, 0, 0}, sidb_layout::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.32}}; + + const auto simulation_results = quickexact(lyt, params); + + for (const auto& layout : simulation_results.charge_distributions) + { + CHECK(round_to_n_decimal_places(layout.get_system_energy(), 1) <= 0); + } +} + +TEMPLATE_TEST_CASE("3 DBs next to each other with automatic base number detection", "[quickexact]", + (cell_level_layout>>)) +{ + using sidb_layout = cell_level_layout>>; + + sidb_layout lyt{{20, 10}}; + + lyt.assign_cell_type({5, 0, 0}, sidb_layout::cell_type::NORMAL); + lyt.assign_cell_type({6, 0, 0}, sidb_layout::cell_type::NORMAL); + lyt.assign_cell_type({7, 0, 0}, sidb_layout::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.32}}; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.additional_simulation_parameters.empty()); + CHECK(simulation_results.additional_simulation_parameters[0].first == "base_number"); + CHECK(std::any_cast(simulation_results.additional_simulation_parameters[0].second) == 3); + + const quickexact_params params_new{sidb_simulation_parameters{2, -0.32}, + automatic_base_number_detection::OFF}; + + const auto simulation_results_new = quickexact(lyt, params_new); + + REQUIRE(!simulation_results_new.additional_simulation_parameters.empty()); + CHECK(simulation_results_new.additional_simulation_parameters[0].first == "base_number"); + CHECK(std::any_cast(simulation_results_new.additional_simulation_parameters[0].second) == 2); +} + +TEMPLATE_TEST_CASE("13 DBs which are all negatively charged", "[quickexact]", + (cell_level_layout>>)) +{ + using sidb_layout = cell_level_layout>>; + + sidb_layout lyt{{20, 10}}; + + lyt.assign_cell_type({26, 10, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({23, 19, 1}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({0, 5, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({38, 10, 1}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({11, 5, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({13, 2, 1}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({40, 19, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({16, 9, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({19, 16, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 8, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({8, 15, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({39, 9, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({30, 15, 0}, TestType::cell_type::NORMAL); + + const quickexact_params params{sidb_simulation_parameters{3, -0.32}}; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + charge_lyt_first.foreach_cell([&](const auto& cell) + { CHECK(charge_lyt_first.get_charge_state(cell) == sidb_charge_state::NEGATIVE); }); + + CHECK(lyt.num_cells() == 13); +} + +TEMPLATE_TEST_CASE("QuickExact simulation of a Y-shape SiDB OR gate with input 01", "[ExGS]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({6, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({8, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({12, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({14, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 5, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({10, 6, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 8, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({16, 1, 0}, TestType::cell_type::NORMAL); + + quickexact_params params{sidb_simulation_parameters{3, -0.28}, automatic_base_number_detection::OFF}; + + SECTION("Standard Physical Parameters") + { + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({6, 2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({12, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 8, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 6, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({16, 1, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 5, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({14, 2, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({8, 3, 0}) == sidb_charge_state::NEUTRAL); + + CHECK_THAT(charge_lyt_first.get_system_energy(), + Catch::Matchers::WithinAbs(0.4662582096, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Increased mu_minus") + { + params.physical_parameters.mu_minus = -0.1; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({6, 2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({12, 3, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({10, 8, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 6, 1}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({16, 1, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 5, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({14, 2, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({8, 3, 0}) == sidb_charge_state::NEUTRAL); + + CHECK_THAT(charge_lyt_first.get_system_energy(), + Catch::Matchers::WithinAbs(0.061037632, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Decreased mu_minus") + { + params.physical_parameters.mu_minus = -0.7; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({6, 2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({12, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 8, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 6, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({16, 1, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 5, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({14, 2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({8, 3, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(charge_lyt_first.get_system_energy(), + Catch::Matchers::WithinAbs(2.069954113, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Decreased lambda_tf") + { + params.physical_parameters.lambda_tf = 1; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({6, 2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({12, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 8, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 6, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({16, 1, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 5, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({14, 2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({8, 3, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(charge_lyt_first.get_system_energy(), + Catch::Matchers::WithinAbs(0.5432404075, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Increased lambda_tf") + { + params.physical_parameters.lambda_tf = 10; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({6, 2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({12, 3, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({10, 8, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 6, 1}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({16, 1, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 5, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({14, 2, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({8, 3, 0}) == sidb_charge_state::NEUTRAL); + + CHECK_THAT(charge_lyt_first.get_system_energy(), + Catch::Matchers::WithinAbs(0.2930574885, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Increased epsilon_r") + { + params.physical_parameters.epsilon_r = 10; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + const auto& charge_lyt_first = simulation_results.charge_distributions.front(); + + CHECK(charge_lyt_first.get_charge_state({6, 2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({12, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 8, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 6, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({16, 1, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_lyt_first.get_charge_state({10, 5, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({14, 2, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_lyt_first.get_charge_state({8, 3, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(charge_lyt_first.get_system_energy(), + Catch::Matchers::WithinAbs(0.505173434, physical_constants::POP_STABILITY_ERR)); + } +} + +TEMPLATE_TEST_CASE("QuickExact simulation of a 3 DB Wire", "[ExGS]", + (cell_level_layout>>)) +{ + TestType lyt{{20, 10}}; + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({8, 0, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({12, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({15, 0, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({19, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({22, 0, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({26, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({29, 0, 0}, TestType::cell_type::NORMAL); + + quickexact_params params{sidb_simulation_parameters{3, -0.28}, automatic_base_number_detection::OFF}; + + SECTION("Standard Physical Parameters") + { + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + + // find the ground state, which is the charge distribution with the lowest energy + const auto ground_state = std::min_element( + simulation_results.charge_distributions.cbegin(), simulation_results.charge_distributions.cend(), + [](const auto& lhs, const auto& rhs) { return lhs.get_system_energy() < rhs.get_system_energy(); }); + + CHECK(ground_state->get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({5, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({8, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({12, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({15, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({19, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({22, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({26, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({29, 0, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(ground_state->get_system_energy(), + Catch::Matchers::WithinAbs(0.274134844, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Increased mu_minus") + { + params.physical_parameters.mu_minus = -0.1; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + + // find the ground state, which is the charge distribution with the lowest energy + const auto ground_state = std::min_element( + simulation_results.charge_distributions.cbegin(), simulation_results.charge_distributions.cend(), + [](const auto& lhs, const auto& rhs) { return lhs.get_system_energy() < rhs.get_system_energy(); }); + + CHECK(ground_state->get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({5, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({8, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({12, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({15, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({19, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({22, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({26, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({29, 0, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(ground_state->get_system_energy(), + Catch::Matchers::WithinAbs(0.0329179963, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Decreased mu_minus") + { + params.physical_parameters.mu_minus = -0.7; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + + // find the ground state, which is the charge distribution with the lowest energy + const auto ground_state = std::min_element( + simulation_results.charge_distributions.cbegin(), simulation_results.charge_distributions.cend(), + [](const auto& lhs, const auto& rhs) { return lhs.get_system_energy() < rhs.get_system_energy(); }); + + CHECK(ground_state->get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({5, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({8, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({12, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({15, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({19, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({22, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({26, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({29, 0, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(ground_state->get_system_energy(), + Catch::Matchers::WithinAbs(1.8649862557, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Decreased lambda_tf") + { + params.physical_parameters.lambda_tf = 1; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + + const auto ground_state = std::min_element( + simulation_results.charge_distributions.cbegin(), simulation_results.charge_distributions.cend(), + [](const auto& lhs, const auto& rhs) { return lhs.get_system_energy() < rhs.get_system_energy(); }); + + CHECK(ground_state->get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({5, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({8, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({12, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({15, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({19, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({22, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({26, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({29, 0, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(ground_state->get_system_energy(), + Catch::Matchers::WithinAbs(0.4606785472, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Increased lambda_tf") + { + params.physical_parameters.lambda_tf = 10; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + + // find the ground state, which is the charge distribution with the lowest energy + const auto ground_state = std::min_element( + simulation_results.charge_distributions.cbegin(), simulation_results.charge_distributions.cend(), + [](const auto& lhs, const auto& rhs) { return lhs.get_system_energy() < rhs.get_system_energy(); }); + + CHECK(ground_state->get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({5, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({8, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({12, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({15, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({19, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({22, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({26, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(ground_state->get_charge_state({29, 0, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(ground_state->get_system_energy(), + Catch::Matchers::WithinAbs(0.3967750406, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Increased epsilon_r") + { + params.physical_parameters.epsilon_r = 10; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + + const auto ground_state = std::min_element( + simulation_results.charge_distributions.cbegin(), simulation_results.charge_distributions.cend(), + [](const auto& lhs, const auto& rhs) { return lhs.get_system_energy() < rhs.get_system_energy(); }); + + CHECK(ground_state->get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({5, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({8, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({12, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({15, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({19, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({22, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({26, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({29, 0, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(ground_state->get_system_energy(), + Catch::Matchers::WithinAbs(1.0443923032, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Decrease epsilon_r, positively charged SiDBs can occur") + { + params.physical_parameters.epsilon_r = 1; + + const auto simulation_results = quickexact(lyt, params); + + REQUIRE(!simulation_results.charge_distributions.empty()); + + const auto ground_state = std::min_element( + simulation_results.charge_distributions.cbegin(), simulation_results.charge_distributions.cend(), + [](const auto& lhs, const auto& rhs) { return lhs.get_system_energy() < rhs.get_system_energy(); }); + + CHECK(ground_state->get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({5, 0, 0}) == sidb_charge_state::POSITIVE); + CHECK(ground_state->get_charge_state({8, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({12, 0, 0}) == sidb_charge_state::POSITIVE); + CHECK(ground_state->get_charge_state({15, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({19, 0, 0}) == sidb_charge_state::POSITIVE); + CHECK(ground_state->get_charge_state({22, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(ground_state->get_charge_state({26, 0, 0}) == sidb_charge_state::POSITIVE); + CHECK(ground_state->get_charge_state({29, 0, 0}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(ground_state->get_system_energy(), + Catch::Matchers::WithinAbs(-5.0592576221, physical_constants::POP_STABILITY_ERR)); + } +} diff --git a/test/algorithms/simulation/sidb/time_to_solution.cpp b/test/algorithms/simulation/sidb/time_to_solution.cpp index 9d37e1ce5..72d694616 100644 --- a/test/algorithms/simulation/sidb/time_to_solution.cpp +++ b/test/algorithms/simulation/sidb/time_to_solution.cpp @@ -14,6 +14,8 @@ #include #include +#include + using namespace fiction; TEMPLATE_TEST_CASE( @@ -27,19 +29,29 @@ TEMPLATE_TEST_CASE( TestType lyt{{20, 10}}; - const sidb_simulation_parameters params{2, -0.30}; - const quicksim_params quicksim_params{params}; - time_to_solution_stats tts_stat{}; - SECTION("layout with no SiDB placed") { - const charge_distribution_surface charge_layout{lyt}; + const sidb_simulation_parameters params{2, -0.30}; + const quicksim_params quicksim_params{params}; + time_to_solution_stats tts_stat_quickexact{}; + const time_to_solution_params tts_params_quickexact{exhaustive_algorithm::QUICKEXACT}; + sim_acc_tts(lyt, quicksim_params, tts_params_quickexact, &tts_stat_quickexact); + + CHECK(tts_stat_quickexact.algorithm == "QuickExact"); + CHECK_THAT(tts_stat_quickexact.acc, Catch::Matchers::WithinAbs(0.0, 0.00001)); + CHECK_THAT(tts_stat_quickexact.time_to_solution, + Catch::Matchers::WithinAbs(std::numeric_limits::max(), 0.00001)); + CHECK(tts_stat_quickexact.mean_single_runtime > 0.0); - sim_acc_tts(charge_layout, quicksim_params, &tts_stat); + time_to_solution_stats tts_stat_exgs{}; + const time_to_solution_params tts_params_exgs{exhaustive_algorithm::EXGS}; + sim_acc_tts(lyt, quicksim_params, tts_params_exgs, &tts_stat_exgs); - CHECK_THAT(tts_stat.acc, Catch::Matchers::WithinAbs(0.0, 0.00001)); - CHECK_THAT(tts_stat.time_to_solution, Catch::Matchers::WithinAbs(std::numeric_limits::max(), 0.00001)); - CHECK(tts_stat.mean_single_runtime > 0.0); + CHECK(tts_stat_exgs.algorithm == "ExGS"); + CHECK_THAT(tts_stat_exgs.acc, Catch::Matchers::WithinAbs(0.0, 0.00001)); + CHECK_THAT(tts_stat_exgs.time_to_solution, + Catch::Matchers::WithinAbs(std::numeric_limits::max(), 0.00001)); + CHECK(tts_stat_exgs.mean_single_runtime > 0.0); } SECTION("layout with seven SiDBs placed") @@ -47,17 +59,43 @@ TEMPLATE_TEST_CASE( 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({2, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({12, 3, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{3, -0.30}; + const quicksim_params quicksim_params{params}; + + const time_to_solution_params tts_params_exgs{exhaustive_algorithm::EXGS}; + time_to_solution_stats tts_stat_exgs{}; + sim_acc_tts(lyt, quicksim_params, tts_params_exgs, &tts_stat_exgs); + + CHECK(tts_stat_exgs.acc == 100); + CHECK(tts_stat_exgs.time_to_solution > 0.0); + CHECK(tts_stat_exgs.mean_single_runtime > 0.0); - lyt.assign_cell_type({6, 3, 0}, TestType::cell_type::NORMAL); - lyt.assign_cell_type({7, 3, 0}, TestType::cell_type::NORMAL); + time_to_solution_stats tts_stat_quickexact{}; + const time_to_solution_params tts_params{exhaustive_algorithm::QUICKEXACT}; + sim_acc_tts(lyt, quicksim_params, tts_params, &tts_stat_quickexact); - lyt.assign_cell_type({6, 10, 0}, TestType::cell_type::NORMAL); - lyt.assign_cell_type({7, 10, 0}, TestType::cell_type::NORMAL); + REQUIRE(tts_stat_quickexact.acc == 100); + CHECK(tts_stat_quickexact.time_to_solution > 0.0); + CHECK(tts_stat_quickexact.mean_single_runtime > 0.0); - sim_acc_tts(lyt, quicksim_params, &tts_stat); + // calculate tts manually. + double tts_calculated = 0.0; - CHECK(tts_stat.acc > 0); - CHECK(tts_stat.time_to_solution > 0.0); - CHECK(tts_stat.mean_single_runtime > 0.0); + if (tts_stat_quickexact.acc == 100) + { + tts_calculated = tts_stat_quickexact.mean_single_runtime; + } + else + { + tts_calculated = (tts_stat_quickexact.mean_single_runtime * std::log(1.0 - tts_params.confidence_level) / + std::log(1.0 - tts_stat_quickexact.acc)); + } + CHECK_THAT(tts_stat_quickexact.time_to_solution - tts_calculated, + Catch::Matchers::WithinAbs(0.0, physical_constants::POP_STABILITY_ERR)); } } diff --git a/test/io/write_location_and_ground_state.cpp b/test/io/write_location_and_ground_state.cpp index ffb6b3c9d..c78466252 100644 --- a/test/io/write_location_and_ground_state.cpp +++ b/test/io/write_location_and_ground_state.cpp @@ -51,7 +51,13 @@ TEST_CASE("writes expected output", "[write_txt_sim_result]") 1.920;0.000;-1;0; 3.072;0.000;-1;-1;)"; - REQUIRE(compare_output(ss.str(), expected_output)); + const std::string expected_output_second = R"(x [nm];y [nm];GS_0;GS_1; + 0.000;0.000;-1;-1; + 1.152;0.000;-1;0; + 1.920;0.000;0;-1; + 3.072;0.000;-1;-1;)"; + + REQUIRE((compare_output(ss.str(), expected_output) || compare_output(ss.str(), expected_output_second))); } SECTION("Output is written to ostream correctly, unique GS") diff --git a/test/technology/charge_distribution_surface.cpp b/test/technology/charge_distribution_surface.cpp index b56fda8dd..d02e76376 100644 --- a/test/technology/charge_distribution_surface.cpp +++ b/test/technology/charge_distribution_surface.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include using namespace fiction; @@ -52,24 +53,335 @@ TEMPLATE_TEST_CASE( } TEMPLATE_TEST_CASE( - "Assign and delete charge states", "[charge-distribution-surface]", + "Assign and delete charge states without defects", "[charge-distribution-surface]", (cell_level_layout>>), (cell_level_layout>>), (cell_level_layout>>), (cell_level_layout>>), - (cell_level_layout>>), - - (sidb_surface>>>), - (sidb_surface>>>), - (sidb_surface>>>), - (sidb_surface< - cell_level_layout>>>), - (sidb_surface< - cell_level_layout>>>)) - + (cell_level_layout>>)) { TestType lyt{{11, 11}}; + SECTION("cell to index and vs") + { + // assign SiDBs and charge states to three different cells and read the charge state + lyt.assign_cell_type({5, 4}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 6}, TestType::cell_type::NORMAL); + const charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + CHECK(charge_layout.cell_to_index({5, 4}) != charge_layout.cell_to_index({5, 5})); + CHECK(charge_layout.cell_to_index({5, 6}) != charge_layout.cell_to_index({5, 5})); + CHECK(charge_layout.index_to_cell(4) == (siqad::coord_t())); + uint64_t found = 0; + for (uint64_t i = 0u; i < 3; i++) + { + if (charge_layout.index_to_cell(i) == (siqad::coord_t(5, 4))) + { + found += 1; + } + } + CHECK(found == 1); + + found = 0; + for (uint64_t i = 0u; i < 3; i++) + { + if (charge_layout.index_to_cell(i) == (siqad::coord_t(5, 5))) + { + found += 1; + } + } + CHECK(found == 1); + + found = 0; + for (uint64_t i = 0u; i < 3; i++) + { + if (charge_layout.index_to_cell(i) == (siqad::coord_t(5, 6))) + { + found += 1; + } + } + CHECK(found == 1); + } + + SECTION("charge distribution defined by a given charge index (base 3)") + { + lyt.assign_cell_type({5, 4}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 6}, TestType::cell_type::NORMAL); + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + charge_layout.assign_charge_index(charge_layout.get_max_charge_index()); + CHECK(charge_layout.get_charge_state({5, 4}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({5, 6}) == sidb_charge_state::POSITIVE); + + charge_layout.assign_charge_index(0); + CHECK(charge_layout.get_charge_state({5, 4}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 6}) == sidb_charge_state::NEGATIVE); + } + + SECTION("charge distribution defined by a given charge index (base 2)") + { + lyt.assign_cell_type({5, 4}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 6}, TestType::cell_type::NORMAL); + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{2}}; + charge_layout.assign_charge_index(charge_layout.get_max_charge_index()); + CHECK(charge_layout.get_charge_state({5, 4}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_state({5, 6}) == sidb_charge_state::NEUTRAL); + + charge_layout.assign_charge_index(0); + CHECK(charge_layout.get_charge_state({5, 4}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 6}) == sidb_charge_state::NEGATIVE); + } + + SECTION("increase charge index of layout") + { + lyt.assign_cell_type({5, 4}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 6}, TestType::cell_type::NORMAL); + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{2}}; + CHECK(charge_layout.get_charge_index_and_base().first == 0); + charge_layout.increase_charge_index_by_one(dependent_cell_mode::FIXED, energy_calculation::UPDATE_ENERGY, + charge_distribution_history::NEGLECT, exhaustive_algorithm::EXGS); + CHECK(charge_layout.get_charge_index_and_base().first == 1); + CHECK(charge_layout.get_charge_state_by_index(0) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state_by_index(1) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state_by_index(2) == sidb_charge_state::NEUTRAL); + charge_layout.assign_charge_index(0); + CHECK(charge_layout.get_charge_state_by_index(0) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state_by_index(1) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state_by_index(2) == sidb_charge_state::NEGATIVE); + + charge_distribution_surface charge_layout_quickexact{lyt, + sidb_simulation_parameters{2}, + sidb_charge_state::NEGATIVE, + {5, 4}}; + charge_layout_quickexact.is_three_state_simulation_required(); + CHECK(charge_layout_quickexact.get_charge_index_and_base().first == 0); + charge_layout_quickexact.increase_charge_index_by_one( + dependent_cell_mode::FIXED, energy_calculation::UPDATE_ENERGY, charge_distribution_history::NEGLECT, + exhaustive_algorithm::QUICKEXACT); + CHECK(charge_layout_quickexact.get_charge_index_and_base().first == 1); + CHECK(charge_layout_quickexact.get_charge_state_by_index(0) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout_quickexact.get_charge_state_by_index(1) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout_quickexact.get_charge_state_by_index(2) == sidb_charge_state::NEUTRAL); + } + + SECTION("charge distribution defined by a given charge index and vs and dependent-cell") + { + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({6, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 5}, TestType::cell_type::NORMAL); + // assign dependent-cell at {5,5} + charge_distribution_surface charge_layout{lyt, + sidb_simulation_parameters{3, -0.25}, + sidb_charge_state::NEGATIVE, + {5, 5}}; + CHECK(charge_layout.get_max_charge_index() == 8); + CHECK(charge_layout.get_max_charge_index_sub_layout() == 0); + + CHECK(charge_layout.get_charge_index_and_base().first == 0); + CHECK(charge_layout.get_charge_index_of_sub_layout() == 0); + + // check if three state simulations are required. + charge_layout.is_three_state_simulation_required(); + + // all SiDBs of the layout can be positively charged. + CHECK(charge_layout.get_max_charge_index() == 0); + // maximal charge index of the sublayout (i.e., SiDBs that can be positively charged). + CHECK(charge_layout.get_max_charge_index_sub_layout() == 8); + + charge_layout.assign_charge_state({5, 5}, sidb_charge_state::POSITIVE); + charge_layout.assign_charge_state({6, 5}, sidb_charge_state::POSITIVE); + charge_layout.assign_charge_state({7, 5}, sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_index_of_sub_layout() == 8); + + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({6, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({7, 5}) == sidb_charge_state::POSITIVE); + + charge_layout.assign_charge_index(0); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({6, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({7, 5}) == sidb_charge_state::NEGATIVE); + + const auto system_energy_maximum = charge_layout.get_system_energy(); + + charge_layout.update_after_charge_change(dependent_cell_mode::VARIABLE, + energy_calculation::KEEP_OLD_ENERGY_VALUE); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({6, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({7, 5}) == sidb_charge_state::NEGATIVE); + + CHECK_THAT(charge_layout.get_system_energy() - system_energy_maximum, Catch::Matchers::WithinAbs(0.0, 0.00001)); + + // update energy and dependent cell is variable with respect to its charge state + charge_layout.update_after_charge_change(dependent_cell_mode::VARIABLE, energy_calculation::UPDATE_ENERGY); + CHECK(charge_layout.get_system_energy() < system_energy_maximum); + + // assign charge states to dependent cell and check that charge index does not change since dependent-cell is + // detached. + charge_layout.assign_charge_state({5, 5}, sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_index_and_base().first == 0); + CHECK(charge_layout.get_charge_index_of_sub_layout() == 0); + + charge_layout.assign_charge_state({5, 5}, sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_index_and_base().first == 0); + CHECK(charge_layout.get_charge_index_of_sub_layout() == 0); + + // assign charge state to cells, but not to the dependent cell + charge_layout.assign_charge_state({6, 5}, sidb_charge_state::POSITIVE, false); + CHECK(charge_layout.get_charge_state({6, 5}) == sidb_charge_state::POSITIVE); + + CHECK(charge_layout.get_charge_index_and_base().first == 0); + CHECK(charge_layout.get_charge_index_of_sub_layout() == 0); + + charge_layout.assign_charge_state({6, 5}, sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({6, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_index_of_sub_layout() == 6); + + charge_layout.assign_charge_state({7, 5}, sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({7, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_index_and_base().first == charge_layout.get_max_charge_index()); + charge_layout.update_after_charge_change(); + CHECK_THAT(charge_layout.get_system_energy() - system_energy_maximum, Catch::Matchers::WithinAbs(0.0, 0.00001)); + + // change charge state of the dependent-cell and check if system energy is reduced + charge_layout.assign_charge_state({5, 5}, sidb_charge_state::NEGATIVE); + charge_layout.update_after_charge_change(); + CHECK(charge_layout.get_system_energy() < system_energy_maximum); + } + + SECTION("cover behavior of dependent_cell in and outside the sublayout (positively charged SiDBs)") + { + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({6, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 4}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 8}, TestType::cell_type::NORMAL); + + // dependent-cell is within the sublayout + charge_distribution_surface charge_layout{lyt, + sidb_simulation_parameters{}, + sidb_charge_state::NEGATIVE, + {5, 5}}; + CHECK(charge_layout.is_three_state_simulation_required()); + charge_layout.assign_charge_state({6, 5}, sidb_charge_state::POSITIVE); + charge_layout.assign_charge_state({7, 5}, sidb_charge_state::POSITIVE); + charge_layout.charge_distribution_to_index(); + CHECK(charge_layout.get_charge_index_of_sub_layout() == 8); + + // dependent-cell is not within the sublayout + charge_distribution_surface charge_layout_dependent_cell_not_in_sublayout{lyt, + sidb_simulation_parameters{}, + sidb_charge_state::NEGATIVE, + {10, 4}}; + CHECK(charge_layout_dependent_cell_not_in_sublayout.is_three_state_simulation_required()); + charge_layout_dependent_cell_not_in_sublayout.assign_charge_state({5, 5}, sidb_charge_state::POSITIVE); + charge_layout_dependent_cell_not_in_sublayout.assign_charge_state({6, 5}, sidb_charge_state::POSITIVE); + charge_layout_dependent_cell_not_in_sublayout.assign_charge_state({7, 5}, sidb_charge_state::POSITIVE); + charge_layout_dependent_cell_not_in_sublayout.assign_charge_state({10, 8}, sidb_charge_state::NEUTRAL); + charge_layout_dependent_cell_not_in_sublayout.charge_distribution_to_index(); + CHECK(charge_layout_dependent_cell_not_in_sublayout.get_charge_index_and_base().first == 8); + CHECK(charge_layout_dependent_cell_not_in_sublayout.get_charge_index_of_sub_layout() == 26); + } + + SECTION("charge index of layout and sublayout (positively charged SiDBs) without dependent-cell") + { + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({6, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 4}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}, sidb_charge_state::NEGATIVE}; + CHECK(charge_layout.is_three_state_simulation_required()); + charge_layout.assign_charge_state({5, 5}, sidb_charge_state::POSITIVE); + charge_layout.assign_charge_state({6, 5}, sidb_charge_state::POSITIVE); + charge_layout.assign_charge_state({7, 5}, sidb_charge_state::POSITIVE); + charge_layout.charge_distribution_to_index(); + CHECK(charge_layout.get_charge_index_of_sub_layout() == 26); + CHECK(charge_layout.get_charge_index_and_base().first == 0); + } + + SECTION("charge index to charge distribution") + { + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({6, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 4}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}, sidb_charge_state::NEGATIVE}; + charge_layout.is_three_state_simulation_required(); + CHECK(charge_layout.get_max_charge_index_sub_layout() == 26); + + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({6, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({7, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({10, 4}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_index_of_sub_layout() == 0); + CHECK(charge_layout.get_charge_index_and_base().first == 0); + + charge_layout.assign_charge_state({5, 5}, sidb_charge_state::POSITIVE); + charge_layout.assign_charge_state({10, 4}, sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({10, 4}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_index_of_sub_layout() == 18); + CHECK(charge_layout.get_charge_index_and_base().first == 16); + + charge_layout.assign_charge_index(0); + + CHECK(charge_layout.get_charge_index_of_sub_layout() == 0); + CHECK(charge_layout.get_charge_index_and_base().first == 0); + + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({10, 4}) == sidb_charge_state::NEGATIVE); + + charge_layout.increase_charge_index_of_sub_layout_by_one(); + CHECK(charge_layout.get_charge_index_of_sub_layout() == 1); + charge_layout.assign_charge_index(1); + CHECK(charge_layout.get_charge_index_and_base().first == 1); + + // set the charge index to zero and thereby, all siDBs to negatively charged. + charge_layout.assign_charge_index(0); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({6, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({7, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({10, 4}) == sidb_charge_state::NEGATIVE); + + charge_layout.assign_charge_index(charge_layout.get_max_charge_index()); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({6, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({7, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({10, 4}) == sidb_charge_state::POSITIVE); + } + + SECTION("positive_cell_to_index, two_state_cell_to_index") + { + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({6, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 4}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}, sidb_charge_state::NEGATIVE}; + charge_layout.is_three_state_simulation_required(); + + CHECK(charge_layout.positive_cell_to_index({5, 5}) == 0); + CHECK(charge_layout.positive_cell_to_index({6, 5}) == 1); + CHECK(charge_layout.positive_cell_to_index({7, 5}) == 2); + CHECK(charge_layout.index_to_three_state_cell(1) == cell(6, 5)); + CHECK(charge_layout.index_to_three_state_cell(4) == cell()); + + CHECK(charge_layout.two_state_cell_to_index({10, 4}) == 0); + CHECK(charge_layout.two_state_cell_to_index({10, 5}) == -1); + CHECK(charge_layout.index_to_two_state_cell(0) == cell(10, 4)); + CHECK(charge_layout.index_to_two_state_cell(1) == cell()); + } + SECTION("assign and read out charge states") { // assign SiDBs and charge states to three different cells and read the charge state @@ -121,7 +433,7 @@ TEMPLATE_TEST_CASE( charge_layout.assign_charge_state({5, 6}, sidb_charge_state::NEGATIVE); // all SiDBs' charge states are set to positive - charge_layout.set_all_charge_states(sidb_charge_state::POSITIVE); + charge_layout.assign_all_charge_states(sidb_charge_state::POSITIVE); // calculate potential between two sidbs (charge sign not included) CHECK(charge_layout.calculate_chargeless_potential_between_sidbs({5, 4}, {5, 5}) > 0.0); @@ -138,10 +450,10 @@ TEMPLATE_TEST_CASE( CHECK(charge_layout.get_charge_state({5, 6}) == sidb_charge_state::POSITIVE); CHECK(charge_layout.get_charge_state({5, 1}) == sidb_charge_state::NONE); - charge_layout.set_all_charge_states(sidb_charge_state::POSITIVE); + charge_layout.assign_all_charge_states(sidb_charge_state::POSITIVE); // all SiDBs' charge states are set to neutral - charge_layout.set_all_charge_states(sidb_charge_state::NEUTRAL); + charge_layout.assign_all_charge_states(sidb_charge_state::NEUTRAL); // read SiDBs' charge states CHECK(charge_layout.get_charge_state({5, 4}) == sidb_charge_state::NEUTRAL); @@ -149,18 +461,18 @@ TEMPLATE_TEST_CASE( CHECK(charge_layout.get_charge_state({5, 6}) == sidb_charge_state::NEUTRAL); CHECK(charge_layout.get_charge_state({5, 1}) == sidb_charge_state::NONE); - charge_layout.set_all_charge_states(sidb_charge_state::NEUTRAL); + charge_layout.assign_all_charge_states(sidb_charge_state::NEUTRAL); // all SiDBs' charge states are set to negative - charge_layout.set_all_charge_states(sidb_charge_state::NEGATIVE); + charge_layout.assign_all_charge_states(sidb_charge_state::NEGATIVE); // read SiDBs' charge states CHECK(charge_layout.get_charge_state({5, 4}) == sidb_charge_state::NEGATIVE); CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NEGATIVE); CHECK(charge_layout.get_charge_state({5, 6}) == sidb_charge_state::NEGATIVE); CHECK(charge_layout.get_charge_state({5, 1}) == sidb_charge_state::NONE); - // - charge_layout.set_all_charge_states(sidb_charge_state::NEGATIVE); + + charge_layout.assign_all_charge_states(sidb_charge_state::NEGATIVE); } SECTION("overwrite the charge state") @@ -194,7 +506,7 @@ TEMPLATE_TEST_CASE( charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; - charge_layout.set_physical_parameters(sidb_simulation_parameters{2, -0.2}); + charge_layout.assign_physical_parameters(sidb_simulation_parameters{2, -0.2}); CHECK(charge_layout.get_phys_params().base == 2); CHECK(charge_layout.get_phys_params().mu_minus == -0.2); CHECK(charge_layout.get_phys_params().epsilon_r == 5.6); @@ -203,7 +515,7 @@ TEMPLATE_TEST_CASE( CHECK(charge_layout.get_phys_params().lat_b == 7.68); CHECK(charge_layout.get_phys_params().lat_c == 2.25); - charge_layout.set_physical_parameters(sidb_simulation_parameters{3, -0.4, 5.1, 5.5, 1, 2, 3}); + charge_layout.assign_physical_parameters(sidb_simulation_parameters{3, -0.4, 5.1, 5.5, 1, 2, 3}); CHECK(charge_layout.get_phys_params().base == 3); CHECK(charge_layout.get_phys_params().mu_minus == -0.4); CHECK(charge_layout.get_phys_params().epsilon_r == 5.1); @@ -222,7 +534,8 @@ TEMPLATE_TEST_CASE( charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; // Take cells that are not part of the layout - CHECK(charge_layout.get_nm_distance_between_cells({3, 0, 0}, {3, 0, 0}) == 0.0); + CHECK_THAT(charge_layout.get_nm_distance_between_cells({3, 0, 0}, {3, 0, 0}), + Catch::Matchers::WithinAbs(0.0, 0.00001)); CHECK_THAT(charge_layout.get_nm_distance_between_cells({0, 0, 0}, {0, 0, 0}), Catch::Matchers::WithinAbs(0.0, 0.00001)); @@ -288,7 +601,7 @@ TEMPLATE_TEST_CASE( { const auto p = charge_layout.get_local_potential(c); REQUIRE(p.has_value()); - CHECK(*p > 0.0); + CHECK(p.value() > 0.0); }); charge_layout.assign_charge_state({0, 0, 0}, sidb_charge_state::NEGATIVE); @@ -302,10 +615,10 @@ TEMPLATE_TEST_CASE( { const auto p = charge_layout.get_local_potential(c); REQUIRE(p.has_value()); - CHECK(*p < 0.0); + CHECK(p.value() < 0.0); }); - charge_layout.set_all_charge_states(sidb_charge_state::NEUTRAL); + charge_layout.assign_all_charge_states(sidb_charge_state::NEUTRAL); charge_layout.update_local_potential(); @@ -314,7 +627,7 @@ TEMPLATE_TEST_CASE( { const auto p = charge_layout.get_local_potential(c); REQUIRE(p.has_value()); - CHECK_THAT((*p), Catch::Matchers::WithinAbs(0.0, 0.00001)); + CHECK_THAT(p.value(), Catch::Matchers::WithinAbs(0.0, 0.00001)); }); } @@ -336,13 +649,13 @@ TEMPLATE_TEST_CASE( CHECK(charge_layout.get_system_energy() > 0.0); // system energy is zero when all SiDBs are neutrally charged. - charge_layout.set_all_charge_states(sidb_charge_state::NEUTRAL); + charge_layout.assign_all_charge_states(sidb_charge_state::NEUTRAL); charge_layout.update_local_potential(); charge_layout.recompute_system_energy(); CHECK_THAT(charge_layout.get_system_energy(), Catch::Matchers::WithinAbs(0.0, 0.00001)); // system energy is zero when all SiDBs are positively charged. - charge_layout.set_all_charge_states(sidb_charge_state::POSITIVE); + charge_layout.assign_all_charge_states(sidb_charge_state::POSITIVE); charge_layout.update_local_potential(); charge_layout.recompute_system_energy(); CHECK(charge_layout.get_system_energy() > 0.0); @@ -350,7 +663,6 @@ TEMPLATE_TEST_CASE( SECTION("Physical validity check, far distance of SIDBs, all NEGATIVE") { - TestType layout{{11, 11}}; layout.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); layout.assign_cell_type({0, 2, 0}, TestType::cell_type::NORMAL); @@ -364,32 +676,30 @@ TEMPLATE_TEST_CASE( charge_layout_five.update_local_potential(); charge_layout_five.recompute_system_energy(); charge_layout_five.validity_check(); - CHECK(charge_layout_five.get_charge_index().first == 0); + CHECK(charge_layout_five.get_charge_index_and_base().first == 0); charge_layout_five.assign_charge_state({4, 1, 1}, sidb_charge_state::POSITIVE); CHECK(charge_layout_five.get_charge_state({4, 1, 1}) == sidb_charge_state::POSITIVE); charge_layout_five.charge_distribution_to_index(); - CHECK(charge_layout_five.get_charge_index().first == 2); + CHECK(charge_layout_five.get_charge_index_and_base().first == 6); charge_layout_five.increase_charge_index_by_one(); - CHECK(charge_layout_five.get_charge_index().first == 3); + CHECK(charge_layout_five.get_charge_index_and_base().first == 7); charge_layout_five.increase_charge_index_by_one(); - CHECK(charge_layout_five.get_charge_index().first == 4); + CHECK(charge_layout_five.get_charge_index_and_base().first == 8); charge_layout_five.increase_charge_index_by_one(); - CHECK(charge_layout_five.get_charge_index().first == 5); + CHECK(charge_layout_five.get_charge_index_and_base().first == 9); charge_layout_five.increase_charge_index_by_one(); - CHECK(charge_layout_five.get_charge_index().first == 6); + CHECK(charge_layout_five.get_charge_index_and_base().first == 10); charge_layout_five.increase_charge_index_by_one(); - CHECK(charge_layout_five.get_charge_index().first == 7); + CHECK(charge_layout_five.get_charge_index_and_base().first == 11); charge_layout_five.increase_charge_index_by_one(); - CHECK(charge_layout_five.get_charge_index().first == 8); + CHECK(charge_layout_five.get_charge_index_and_base().first == 12); charge_layout_five.increase_charge_index_by_one(); - CHECK(charge_layout_five.get_charge_index().first == 9); - charge_layout_five.increase_charge_index_by_one(); - CHECK(charge_layout_five.get_charge_index().first == 10); + CHECK(charge_layout_five.get_charge_index_and_base().first == 13); CHECK(charge_layout_five.get_charge_state({0, 0, 0}) == sidb_charge_state::NEUTRAL); - CHECK(charge_layout_five.get_charge_state({0, 2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout_five.get_charge_state({0, 2, 0}) == sidb_charge_state::NEUTRAL); CHECK(charge_layout_five.get_charge_state({4, 1, 1}) == sidb_charge_state::NEUTRAL); } @@ -411,6 +721,130 @@ TEMPLATE_TEST_CASE( CHECK(!charge_layout.is_physically_valid()); } + SECTION("apply external voltage at two cells") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{3, -0.32}; + + lyt_new.assign_cell_type({0, 0, 1}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 5, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout_new{lyt_new, + params, + sidb_charge_state::NEUTRAL, + {}, + {{{0, 0, 1}, -0.5}}}; + REQUIRE(!charge_layout_new.get_external_potentials().empty()); + CHECK(charge_layout_new.get_external_potentials().size() == 1); + CHECK(charge_layout_new.get_external_potentials().size() == 1); + + REQUIRE(charge_layout_new.get_local_potential({0, 0, 1}).has_value()); + CHECK_THAT(charge_layout_new.get_local_potential({0, 0, 1}).value() + 0.5, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + REQUIRE(charge_layout_new.get_local_potential({1, 3, 0}).has_value()); + CHECK_THAT(charge_layout_new.get_local_potential({1, 3, 0}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + REQUIRE(charge_layout_new.get_local_potential({10, 5, 1}).has_value()); + CHECK_THAT(charge_layout_new.get_local_potential({10, 5, 1}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + charge_layout_new.assign_all_charge_states(sidb_charge_state::POSITIVE); + charge_layout_new.update_after_charge_change(); + CHECK(charge_layout_new.get_charge_state({0, 0, 1}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout_new.get_charge_state({1, 3, 0}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout_new.get_charge_state({10, 5, 1}) == sidb_charge_state::POSITIVE); + + CHECK(charge_layout_new.get_local_potential({0, 0, 1}).value() > -0.5); + CHECK(charge_layout_new.get_local_potential({1, 3, 0}).value() > -0.5); + CHECK(charge_layout_new.get_local_potential({10, 5, 1}).value() > -0.5); + + charge_layout_new.assign_all_charge_states(sidb_charge_state::NEUTRAL); + + charge_layout_new.assign_local_external_potential({{{0, 0, 1}, -0.1}}); + REQUIRE(charge_layout_new.get_local_potential({0, 0, 1}).has_value()); + CHECK_THAT(charge_layout_new.get_local_potential({0, 0, 1}).value() + 0.1, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(charge_layout_new.get_local_potential({1, 3, 0}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + CHECK_THAT(charge_layout_new.get_local_potential({10, 5, 1}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + + charge_layout_new.assign_local_external_potential({{{0, 0, 1}, -0.5}, {{10, 5, 1}, -0.1}}); + charge_layout_new.assign_all_charge_states(sidb_charge_state::NEGATIVE); + charge_layout_new.update_after_charge_change(); + + CHECK(charge_layout_new.get_local_potential({0, 0, 1}).value() < -0.5); + CHECK(charge_layout_new.get_local_potential({10, 5, 1}).value() < -0.1); + CHECK(charge_layout_new.get_local_potential({1, 3, 0}).value() < 0); + } + + SECTION("apply homogenous external voltage to layout") + { + const sidb_simulation_parameters params{3, -0.32}; + lyt.assign_cell_type({0, 0, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 5, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, params, sidb_charge_state::NEUTRAL}; + CHECK(charge_layout.get_external_potentials().empty()); + charge_layout.assign_global_external_potential(-0.1); + CHECK(!charge_layout.get_external_potentials().empty()); + + REQUIRE(charge_layout.get_local_potential({0, 0, 1}).has_value()); + CHECK_THAT(charge_layout.get_local_potential({0, 0, 1}).value() + 0.1, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + REQUIRE(charge_layout.get_local_potential({1, 3, 0}).has_value()); + CHECK_THAT(charge_layout.get_local_potential({1, 3, 0}).value() + 0.1, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + REQUIRE(charge_layout.get_local_potential({10, 5, 1}).has_value()); + CHECK_THAT(charge_layout.get_local_potential({10, 5, 1}).value() + 0.1, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + + lyt.assign_cell_type({0, 0, 1}, TestType::cell_type::EMPTY); + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::EMPTY); + lyt.assign_cell_type({10, 5, 1}, TestType::cell_type::EMPTY); + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + charge_distribution_surface charge_layout_new{lyt, params, sidb_charge_state::NEGATIVE}; + charge_layout_new.assign_charge_state({3, 0, 0}, sidb_charge_state::NEUTRAL); + charge_layout_new.assign_charge_state({5, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.update_after_charge_change(); + CHECK(charge_layout_new.is_physically_valid()); + + charge_layout_new.assign_global_external_potential(0.2); + CHECK(!charge_layout_new.is_physically_valid()); + + charge_layout_new.assign_charge_state({0, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.assign_charge_state({3, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.assign_charge_state({5, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.update_after_charge_change(); + CHECK(charge_layout_new.is_physically_valid()); + } + + SECTION("no external voltage given") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{3, -0.32}; + + lyt_new.assign_cell_type({0, 0, 1}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 5, 1}, TestType::cell_type::NORMAL); + + const charge_distribution_surface charge_layout_new{lyt_new, params, sidb_charge_state::NEUTRAL}; + + REQUIRE(charge_layout_new.get_local_potential({0, 0, 1}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({1, 3, 0}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({10, 5, 1}).has_value()); + + CHECK_THAT(charge_layout_new.get_local_potential({0, 0, 1}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + CHECK_THAT(charge_layout_new.get_local_potential({1, 3, 0}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + CHECK_THAT(charge_layout_new.get_local_potential({10, 5, 1}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + } + SECTION("increase charge index") { TestType lyt_new{{11, 11}}; @@ -423,29 +857,64 @@ TEMPLATE_TEST_CASE( charge_distribution_surface charge_layout_new{lyt_new, params}; const auto negative_sidbs = charge_layout_new.negative_sidb_detection(); REQUIRE(negative_sidbs.size() == 3); - CHECK(negative_sidbs[0] == 0); - CHECK(negative_sidbs[1] == 1); - CHECK(negative_sidbs[2] == 2); - CHECK(charge_layout_new.get_charge_index().first == 0); + CHECK(charge_layout_new.get_charge_index_and_base().first == 0); - charge_layout_new.set_all_charge_states(sidb_charge_state::POSITIVE); + charge_layout_new.assign_all_charge_states(sidb_charge_state::POSITIVE); CHECK(charge_layout_new.get_charge_state({0, 0, 1}) == sidb_charge_state::POSITIVE); CHECK(charge_layout_new.get_charge_state({1, 3, 0}) == sidb_charge_state::POSITIVE); CHECK(charge_layout_new.get_charge_state({10, 5, 1}) == sidb_charge_state::POSITIVE); charge_layout_new.charge_distribution_to_index(); - CHECK(charge_layout_new.get_charge_index().first == 26); + CHECK(charge_layout_new.get_charge_index_and_base().first == 26); - charge_layout_new.set_all_charge_states(sidb_charge_state::NEUTRAL); + charge_layout_new.assign_all_charge_states(sidb_charge_state::NEUTRAL); charge_layout_new.charge_distribution_to_index(); - CHECK(charge_layout_new.get_charge_index().first == 13); + CHECK(charge_layout_new.get_charge_index_and_base().first == 13); charge_layout_new.increase_charge_index_by_one(); charge_layout_new.charge_distribution_to_index(); - CHECK(charge_layout_new.get_charge_index().first == 14); + CHECK(charge_layout_new.get_charge_index_and_base().first == 14); charge_layout_new.increase_charge_index_by_one(); - CHECK(charge_layout_new.get_charge_index().first == 15); + CHECK(charge_layout_new.get_charge_index_and_base().first == 15); + } + + SECTION("detecting perturber in layout with only one SiDB") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{3, -0.32}; + + lyt_new.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout_new{lyt_new, params}; + const auto negative_sidbs = charge_layout_new.negative_sidb_detection(); + REQUIRE(negative_sidbs.size() == 1); + } + + SECTION("Seven randomly distributed DB | checking for physical validity") + { + TestType lyt_new{{11, 11}}; + 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); + const sidb_simulation_parameters params{2, -0.28}; + charge_distribution_surface charge_layout_new{lyt, params}; + charge_layout_new.assign_charge_state({1, 3, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.assign_charge_state({3, 3, 0}, sidb_charge_state::NEUTRAL); + charge_layout_new.assign_charge_state({4, 3, 0}, sidb_charge_state::NEUTRAL); + charge_layout_new.assign_charge_state({6, 3, 0}, sidb_charge_state::NEUTRAL); + charge_layout_new.assign_charge_state({7, 3, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.assign_charge_state({6, 10, 0}, sidb_charge_state::NEUTRAL); + charge_layout_new.assign_charge_state({7, 10, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.update_after_charge_change(); + + REQUIRE(charge_layout_new.is_physically_valid()); } SECTION("using chargeless and normal potential function") @@ -471,6 +940,9 @@ TEMPLATE_TEST_CASE( charge_layout_new.calculate_chargeless_potential_between_sidbs({0, 0, 1}, {1, 3, 0}), Catch::Matchers::WithinAbs(0.0, 0.000001)); + CHECK(charge_layout_new.get_chargeless_potential_between_sidbs({0, 0, 0}, {0, 0, 1}) == 0.0); + CHECK(charge_layout_new.get_potential_between_sidbs({0, 0, 0}, {0, 0, 1}) == 0.0); + CHECK(charge_layout_new.get_chargeless_potential_between_sidbs({0, 0, 1}, {0, 0, 1}) == 0.0); CHECK(charge_layout_new.get_potential_between_sidbs({0, 0, 1}, {0, 0, 1}) == 0.0); @@ -492,4 +964,1119 @@ TEMPLATE_TEST_CASE( charge_layout_new.get_chargeless_potential_between_sidbs({0, 0, 1}, {1, 3, 0}), Catch::Matchers::WithinAbs(0.0, 0.000001)); } + + SECTION("adding dependent cell") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{3, -0.32}; + + lyt_new.assign_cell_type({0, 3, 1}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 4, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout_new{lyt_new, params, sidb_charge_state::NEGATIVE, {10, 4, 1}}; + + charge_layout_new.assign_charge_state({0, 3, 1}, sidb_charge_state::NEGATIVE); + charge_layout_new.assign_charge_state({1, 3, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.assign_charge_state({10, 4, 1}, sidb_charge_state::NEUTRAL); + + CHECK(charge_layout_new.get_charge_state({0, 3, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout_new.get_charge_state({1, 3, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout_new.get_charge_state({10, 4, 1}) == sidb_charge_state::NEUTRAL); + charge_layout_new.update_after_charge_change(); + + REQUIRE(charge_layout_new.get_local_potential({0, 3, 1}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({1, 3, 0}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({10, 4, 1}).has_value()); + + CHECK(charge_layout_new.get_local_potential({0, 3, 1}).value() < 0); + CHECK(charge_layout_new.get_local_potential({1, 3, 0}).value() < 0); + CHECK(charge_layout_new.get_local_potential({10, 4, 1}).value() < 0); + charge_layout_new.update_charge_state_of_dependent_cell(); + CHECK(charge_layout_new.get_charge_state({10, 4, 1}) == sidb_charge_state::NEGATIVE); + } + + SECTION("adding dependent cell and increase index") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{2, -0.32}; + + lyt_new.assign_cell_type({0, 3, 1}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 4, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout_new{lyt_new, params, sidb_charge_state::NEGATIVE, {10, 4, 1}}; + + REQUIRE(charge_layout_new.get_local_potential({0, 3, 1}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({1, 3, 0}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({10, 4, 1}).has_value()); + + CHECK(charge_layout_new.get_local_potential({0, 3, 1}).value() < 0); + CHECK(charge_layout_new.get_local_potential({1, 3, 0}).value() < 0); + CHECK(charge_layout_new.get_local_potential({10, 4, 1}).value() < 0); + + for (uint64_t i = 0; i < 3; i++) + { + charge_layout_new.increase_charge_index_by_one(); + CHECK(charge_layout_new.get_charge_state({10, 4, 1}) == sidb_charge_state::NEGATIVE); + } + } + + SECTION("adding dependent cell and compare local potential and system energy") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{2, -0.32}; + + lyt_new.assign_cell_type({0, 3, 1}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 4, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout_new{lyt_new, params, sidb_charge_state::NEGATIVE, {10, 4, 1}}; + + charge_layout_new.assign_charge_state({0, 3, 1}, sidb_charge_state::NEGATIVE); + charge_layout_new.assign_charge_state({1, 3, 0}, sidb_charge_state::NEUTRAL); + charge_layout_new.assign_charge_state({10, 4, 1}, sidb_charge_state::NEUTRAL); + charge_layout_new.update_after_charge_change(); + + REQUIRE(charge_layout_new.get_local_potential({0, 3, 1}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({1, 3, 0}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({10, 4, 1}).has_value()); + + CHECK(charge_layout_new.get_local_potential({0, 3, 1}).value() == 0.0); + CHECK(charge_layout_new.get_local_potential({1, 3, 0}).value() < 0); + CHECK(charge_layout_new.get_local_potential({10, 4, 1}).value() < 0); + + charge_layout_new.update_charge_state_of_dependent_cell(); + CHECK(charge_layout_new.get_charge_state({0, 3, 1}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout_new.get_charge_state({1, 3, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout_new.get_charge_state({10, 4, 1}) == sidb_charge_state::NEGATIVE); + const auto loc_one = charge_layout_new.get_local_potential({0, 3, 1}).value(); + const auto loc_two = charge_layout_new.get_local_potential({1, 3, 0}).value(); + const auto loc_three = charge_layout_new.get_local_potential({10, 4, 1}).value(); + CHECK(loc_one < 0); + CHECK(loc_two < 0); + CHECK(loc_three < 0); + charge_layout_new.recompute_system_energy(); + auto system_energy_first = charge_layout_new.get_system_energy(); + + charge_layout_new.assign_charge_state({0, 3, 1}, sidb_charge_state::NEGATIVE); + charge_layout_new.assign_charge_state({1, 3, 0}, sidb_charge_state::NEUTRAL); + charge_layout_new.assign_charge_state({10, 4, 1}, sidb_charge_state::NEGATIVE); + charge_layout_new.update_after_charge_change(); + CHECK_THAT(loc_one - charge_layout_new.get_local_potential({0, 3, 1}).value(), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(loc_two - charge_layout_new.get_local_potential({1, 3, 0}).value(), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(loc_three - charge_layout_new.get_local_potential({10, 4, 1}).value(), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + auto system_energy_second = charge_layout_new.get_system_energy(); + CHECK_THAT(system_energy_first - system_energy_second, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("Physical validity check after dependent cell is updated") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, + sidb_simulation_parameters{}, + sidb_charge_state::NEGATIVE, + {3, 0, 0}}; + CHECK(charge_layout.get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({3, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(!charge_layout.is_physically_valid()); + + charge_layout.update_charge_state_of_dependent_cell(); + charge_layout.update_after_charge_change(dependent_cell_mode::VARIABLE); + charge_layout.validity_check(); + CHECK(charge_layout.is_physically_valid()); + } + + SECTION("check charge index") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{2, -0.32}; + + charge_distribution_surface charge_layout{lyt, params, sidb_charge_state::NEGATIVE, {3, 0, 0}}; + CHECK(charge_layout.get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({3, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_index_and_base().first == 0); + + charge_layout.increase_charge_index_by_one(); + CHECK(charge_layout.get_charge_index_and_base().first == 1); + charge_layout.increase_charge_index_by_one(); + CHECK(charge_layout.get_charge_index_and_base().first == 2); + charge_layout.increase_charge_index_by_one(); + CHECK(charge_layout.get_charge_index_and_base().first == 3); + + CHECK(charge_layout.get_charge_state({0, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_state({5, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_index_and_base().first == 3); + } + + SECTION("Physical validity check after dependent cell is updated") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, + sidb_simulation_parameters{}, + sidb_charge_state::NEGATIVE, + {3, 0, 0}}; + CHECK(charge_layout.get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({3, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(!charge_layout.is_physically_valid()); + + charge_layout.update_charge_state_of_dependent_cell(); + charge_layout.update_after_charge_change(dependent_cell_mode::VARIABLE); + charge_layout.validity_check(); + CHECK(charge_layout.is_physically_valid()); + } + + SECTION("check charge index") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{2, -0.32}; + + charge_distribution_surface charge_layout{lyt, params, sidb_charge_state::NEGATIVE, {0, 0, 0}}; + CHECK(charge_layout.get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({3, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_index_and_base().first == 0); + + charge_layout.increase_charge_index_by_one(); + CHECK(charge_layout.get_charge_index_and_base().first == 1); + charge_layout.increase_charge_index_by_one(); + CHECK(charge_layout.get_charge_index_and_base().first == 2); + charge_layout.increase_charge_index_by_one(); + CHECK(charge_layout.get_charge_index_and_base().first == 3); + + CHECK(charge_layout.get_charge_state({3, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_state({5, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_index_and_base().first == 3); + } + + SECTION("Y-shape SiDB OR gate with input 01 and global external potential (high)") + { + + lyt.assign_cell_type({6, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({8, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({12, 3, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({14, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 5, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({10, 6, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 8, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({16, 1, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{3, -0.28}; + charge_distribution_surface charge_lyt_first{lyt, params, sidb_charge_state::POSITIVE}; + charge_lyt_first.assign_global_external_potential(-2.0); + CHECK(charge_lyt_first.is_physically_valid()); + } + + SECTION("detecting DBs which could be positively charged due to maximal band bending") + { + + lyt.assign_cell_type({6, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({7, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({8, 2, 0}, TestType::cell_type::NORMAL); + + lyt.assign_cell_type({10, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({20, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({23, 2, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{3, -0.28}; + charge_distribution_surface charge_lyt_first{lyt, params, sidb_charge_state::NEGATIVE}; + charge_lyt_first.is_three_state_simulation_required(); + const auto positive_candidates = charge_lyt_first.get_positive_candidates(); + REQUIRE(positive_candidates.size() == 3); + uint64_t loop_counter = 0; + for (const auto& cell : positive_candidates) + { + if (cell == siqad::coord_t(6, 2, 0)) + { + loop_counter += 1; + } + } + CHECK(loop_counter == 1); + + uint64_t loop_counter_second = 0; + for (const auto& cell : positive_candidates) + { + if (cell == siqad::coord_t(7, 2, 0)) + { + loop_counter_second += 1; + } + } + CHECK(loop_counter_second == 1); + + uint64_t loop_counter_third = 0; + for (const auto& cell : positive_candidates) + { + if (cell == siqad::coord_t(7, 2, 0)) + { + loop_counter_third += 1; + } + } + CHECK(loop_counter_third == 1); + } + + SECTION("detecting DBs which could be positively charged due to maximal band bending") + { + + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({4, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({6, 0, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{3, -0.32}; + charge_distribution_surface charge_lyt_first{lyt, params, sidb_charge_state::NEGATIVE}; + charge_lyt_first.assign_charge_state({0, 0, 0}, sidb_charge_state::NEGATIVE); + charge_lyt_first.assign_charge_state({4, 0, 0}, sidb_charge_state::NEUTRAL); + charge_lyt_first.assign_charge_state({6, 0, 0}, sidb_charge_state::NEGATIVE); + charge_lyt_first.update_after_charge_change(); + } +} + +TEMPLATE_TEST_CASE( + "Assign and delete charge states without defects, part one", "[charge-distribution-surface]", + (sidb_surface>>>), + (sidb_surface>>>), + (sidb_surface>>>), + (sidb_surface< + cell_level_layout>>>), + (sidb_surface< + cell_level_layout>>>)) + +{ + TestType lyt{{11, 11}}; + + SECTION("Assign defect") + { + lyt.assign_cell_type({5, 4}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 6}, TestType::cell_type::NORMAL); + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + charge_layout.add_sidb_defect_to_potential_landscape( + {5, 6}, sidb_defect{sidb_defect_type::UNKNOWN, -1, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf}); + } + + SECTION("perturber is replaced by an equivalent defect") + { + lyt.assign_cell_type({5, 4}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 6}, TestType::cell_type::NORMAL); + const charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + lyt.assign_cell_type({5, 6}, TestType::cell_type::EMPTY); + charge_distribution_surface charge_layout_new{lyt, sidb_simulation_parameters{}}; + charge_layout_new.add_sidb_defect_to_potential_landscape( + {5, 6}, sidb_defect{sidb_defect_type::UNKNOWN, -1, charge_layout_new.get_phys_params().epsilon_r, + charge_layout_new.get_phys_params().lambda_tf}); + CHECK_THAT(charge_layout_new.chargeless_potential_generated_by_defect_at_given_distance(0.0), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + charge_layout_new.update_after_charge_change(); + CHECK_THAT(charge_layout.get_system_energy() - charge_layout_new.get_system_energy(), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + } + + SECTION("overwrite an assigned charge state") + { + // assign SiDBs and charge states to three different cells + lyt.assign_cell_type({5, 4}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 6}, TestType::cell_type::NORMAL); + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + charge_layout.assign_charge_state({5, 4}, sidb_charge_state::POSITIVE); + charge_layout.assign_charge_state({5, 5}, sidb_charge_state::NEUTRAL); + charge_layout.assign_charge_state({5, 6}, sidb_charge_state::NEGATIVE); + + // all SiDBs' charge states are set to positive + charge_layout.assign_all_charge_states(sidb_charge_state::POSITIVE); + + // calculate potential between two sidbs (charge sign not included) + CHECK(charge_layout.calculate_chargeless_potential_between_sidbs({5, 4}, {5, 5}) > 0.0); + CHECK_THAT(charge_layout.calculate_chargeless_potential_between_sidbs({5, 4}, {5, 4}), + Catch::Matchers::WithinAbs(0.0, 0.00001)); + CHECK(charge_layout.calculate_chargeless_potential_between_sidbs({5, 4}, {5, 6}) > 0); + CHECK(charge_layout.calculate_chargeless_potential_between_sidbs({5, 5}, {5, 6}) > 0); + CHECK_THAT(charge_layout.calculate_chargeless_potential_between_sidbs({5, 6}, {5, 5}) - + charge_layout.calculate_chargeless_potential_between_sidbs({5, 5}, {5, 6}), + Catch::Matchers::WithinAbs(0.0, 0.00001)); + // read SiDBs' charge states + CHECK(charge_layout.get_charge_state({5, 4}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({5, 6}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({5, 1}) == sidb_charge_state::NONE); + + charge_layout.assign_all_charge_states(sidb_charge_state::POSITIVE); + + // all SiDBs' charge states are set to neutral + charge_layout.assign_all_charge_states(sidb_charge_state::NEUTRAL); + + // read SiDBs' charge states + CHECK(charge_layout.get_charge_state({5, 4}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_state({5, 6}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_state({5, 1}) == sidb_charge_state::NONE); + + charge_layout.assign_all_charge_states(sidb_charge_state::NEUTRAL); + + // all SiDBs' charge states are set to negative + charge_layout.assign_all_charge_states(sidb_charge_state::NEGATIVE); + + // read SiDBs' charge states + CHECK(charge_layout.get_charge_state({5, 4}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 6}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 1}) == sidb_charge_state::NONE); + + charge_layout.assign_all_charge_states(sidb_charge_state::NEGATIVE); + } + + SECTION("overwrite the charge state") + { + // assign SiDBs and charge states to three different cells + lyt.assign_cell_type({5, 4}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 6}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + + charge_layout.assign_charge_state({5, 4}, sidb_charge_state::POSITIVE); + charge_layout.assign_charge_state({5, 5}, sidb_charge_state::NEUTRAL); + charge_layout.assign_charge_state({5, 6}, sidb_charge_state::NEGATIVE); + + // remove previously assigned charge states + charge_layout.assign_charge_state({5, 4}, sidb_charge_state::NONE); + charge_layout.assign_charge_state({5, 5}, sidb_charge_state::NONE); + charge_layout.assign_charge_state({5, 6}, sidb_charge_state::POSITIVE); + CHECK(charge_layout.get_charge_state({5, 4}) == sidb_charge_state::NONE); + CHECK(charge_layout.get_charge_state({5, 5}) == sidb_charge_state::NONE); + CHECK(charge_layout.get_charge_state({5, 6}) == sidb_charge_state::POSITIVE); + } + + SECTION("set physical simulation parameters") + { + // assign SiDBs and charge states to three different cells + lyt.assign_cell_type({5, 4}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 5}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 6}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + + charge_layout.assign_physical_parameters(sidb_simulation_parameters{2, -0.2}); + CHECK(charge_layout.get_phys_params().base == 2); + CHECK(charge_layout.get_phys_params().mu_minus == -0.2); + CHECK(charge_layout.get_phys_params().epsilon_r == 5.6); + CHECK(charge_layout.get_phys_params().lambda_tf == 5.0); + CHECK(charge_layout.get_phys_params().lat_a == 3.84); + CHECK(charge_layout.get_phys_params().lat_b == 7.68); + CHECK(charge_layout.get_phys_params().lat_c == 2.25); + + charge_layout.assign_physical_parameters(sidb_simulation_parameters{3, -0.4, 5.1, 5.5, 1, 2, 3}); + CHECK(charge_layout.get_phys_params().base == 3); + CHECK(charge_layout.get_phys_params().mu_minus == -0.4); + CHECK(charge_layout.get_phys_params().epsilon_r == 5.1); + CHECK(charge_layout.get_phys_params().lambda_tf == 5.5); + CHECK(charge_layout.get_phys_params().lat_a == 1); + CHECK(charge_layout.get_phys_params().lat_b == 2); + CHECK(charge_layout.get_phys_params().lat_c == 3); + } + + SECTION("Distance matrix") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 1, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + + // Take cells that are not part of the layout + CHECK(charge_layout.get_nm_distance_between_cells({3, 0, 0}, {3, 0, 0}) == 0.0); + + CHECK_THAT(charge_layout.get_nm_distance_between_cells({0, 0, 0}, {0, 0, 0}), + Catch::Matchers::WithinAbs(0.0, 0.00001)); + CHECK_THAT(charge_layout.get_nm_distance_between_cells({0, 0, 0}, {1, 0, 0}), + Catch::Matchers::WithinAbs((sidb_simulation_parameters{}.lat_a * 0.1), 0.00001)); + CHECK_THAT(charge_layout.get_nm_distance_between_cells({1, 0, 0}, {0, 0, 0}), + Catch::Matchers::WithinAbs((sidb_simulation_parameters{}.lat_a * 0.1), 0.00001)); + CHECK_THAT(charge_layout.get_nm_distance_between_cells({1, 0, 0}, {1, 0, 0}), + Catch::Matchers::WithinAbs(0.0, 0.00001)); + CHECK_THAT(charge_layout.get_nm_distance_between_cells({0, 0, 0}, {1, 1, 1}), + Catch::Matchers::WithinAbs( + std::hypot(sidb_simulation_parameters{}.lat_a * 0.1, + sidb_simulation_parameters{}.lat_b * 0.1 + sidb_simulation_parameters{}.lat_c * 0.1), + 0.00001)); + CHECK_THAT(charge_layout.get_nm_distance_between_cells({1, 1, 1}, {1, 1, 1}), + Catch::Matchers::WithinAbs(0.0, 0.00001)); + } + + SECTION("Potential matrix") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 8, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 10, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + + CHECK_THAT(charge_layout.get_chargeless_potential_between_sidbs({2, 8, 0}, {2, 10, 1}), + Catch::Matchers::WithinAbs(0.0, 0.00001)); + CHECK_THAT(charge_layout.get_chargeless_potential_between_sidbs({0, 0, 0}, {0, 0, 0}), + Catch::Matchers::WithinAbs(0.0, 0.00001)); + CHECK_THAT(charge_layout.get_chargeless_potential_between_sidbs({1, 8, 0}, {1, 8, 0}), + Catch::Matchers::WithinAbs(0.0, 0.00001)); + CHECK_THAT(charge_layout.get_chargeless_potential_between_sidbs({1, 10, 1}, {1, 10, 1}), + Catch::Matchers::WithinAbs(0.0, 0.00001)); + CHECK_THAT(charge_layout.get_chargeless_potential_between_sidbs({1, 8, 0}, {0, 0, 0}), + Catch::Matchers::WithinAbs(0.0121934043, 0.00001)); + CHECK_THAT(std::abs(charge_layout.get_chargeless_potential_between_sidbs({0, 0, 0}, {1, 10, 1}) - + charge_layout.get_chargeless_potential_between_sidbs({1, 10, 1}, {0, 0, 0})), + Catch::Matchers::WithinAbs(0.0, 0.00001)); + + CHECK(charge_layout.get_chargeless_potential_between_sidbs({0, 0, 0}, {1, 8, 0}) > + charge_layout.get_chargeless_potential_between_sidbs({1, 10, 1}, {0, 0, 0})); + } + + SECTION("Local Potential") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 8, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 10, 1}, TestType::cell_type::NORMAL); + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + charge_layout.assign_charge_state({0, 0, 0}, sidb_charge_state::POSITIVE); + charge_layout.assign_charge_state({1, 8, 0}, sidb_charge_state::POSITIVE); + charge_layout.assign_charge_state({1, 10, 1}, sidb_charge_state::POSITIVE); + + charge_layout.update_local_potential(); + + REQUIRE(charge_layout.get_local_potential({0, 0, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({1, 8, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({1, 10, 1}).has_value()); + + // cell and index are not part of the layout. + CHECK(!charge_layout.get_local_potential({2, 0, 0}).has_value()); + CHECK(!charge_layout.get_local_potential_by_index(4).has_value()); + + charge_layout.foreach_cell( + [&charge_layout](const auto& c) + { + const auto p = charge_layout.get_local_potential(c); + REQUIRE(p.has_value()); + CHECK(*p > 0.0); + }); + + charge_layout.assign_charge_state({0, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout.assign_charge_state({1, 8, 0}, sidb_charge_state::NEGATIVE); + charge_layout.assign_charge_state({1, 10, 1}, sidb_charge_state::NEGATIVE); + + charge_layout.update_local_potential(); + + charge_layout.foreach_cell( + [&charge_layout](const auto& c) + { + const auto p = charge_layout.get_local_potential(c); + REQUIRE(p.has_value()); + CHECK(p.value() < 0.0); + }); + + charge_layout.assign_all_charge_states(sidb_charge_state::NEUTRAL); + + charge_layout.update_local_potential(); + + charge_layout.foreach_cell( + [&charge_layout](const auto& c) + { + const auto p = charge_layout.get_local_potential(c); + REQUIRE(p.has_value()); + CHECK_THAT(p.value(), Catch::Matchers::WithinAbs(0.0, 0.00001)); + }); + } +} + +TEMPLATE_TEST_CASE( + "Assign and delete charge states without defects, part two", "[charge-distribution-surface]", + (sidb_surface>>>), + (sidb_surface>>>), + (sidb_surface>>>), + (sidb_surface< + cell_level_layout>>>), + (sidb_surface< + cell_level_layout>>>)) + +{ + TestType lyt{{11, 11}}; + + SECTION("Electrostatic potential energy of the charge configuration") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 1, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + + charge_layout.assign_charge_state({0, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout.assign_charge_state({1, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout.assign_charge_state({1, 1, 1}, sidb_charge_state::NEGATIVE); + + // system energy is zero when all SiDBs are positively charged. + charge_layout.update_local_potential(); + charge_layout.recompute_system_energy(); + CHECK(charge_layout.get_system_energy() > 0.0); + + // system energy is zero when all SiDBs are neutrally charged. + charge_layout.assign_all_charge_states(sidb_charge_state::NEUTRAL); + charge_layout.update_local_potential(); + charge_layout.recompute_system_energy(); + CHECK_THAT(charge_layout.get_system_energy(), Catch::Matchers::WithinAbs(0.0, 0.00001)); + + // system energy is zero when all SiDBs are positively charged. + charge_layout.assign_all_charge_states(sidb_charge_state::POSITIVE); + charge_layout.update_local_potential(); + charge_layout.recompute_system_energy(); + CHECK(charge_layout.get_system_energy() > 0.0); + } + + SECTION("Physical validity check, far distance of SIDBs, all NEGATIVE") + { + + TestType layout{{11, 11}}; + layout.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + layout.assign_cell_type({0, 2, 0}, TestType::cell_type::NORMAL); + layout.assign_cell_type({4, 1, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout_five{layout, sidb_simulation_parameters{}}; + CHECK(charge_layout_five.get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout_five.get_charge_state({0, 2, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout_five.get_charge_state({4, 1, 1}) == sidb_charge_state::NEGATIVE); + + charge_layout_five.update_local_potential(); + charge_layout_five.recompute_system_energy(); + charge_layout_five.validity_check(); + CHECK(charge_layout_five.get_charge_index_and_base().first == 0); + + charge_layout_five.assign_charge_state({4, 1, 1}, sidb_charge_state::POSITIVE); + CHECK(charge_layout_five.get_charge_state({4, 1, 1}) == sidb_charge_state::POSITIVE); + charge_layout_five.charge_distribution_to_index(); + CHECK(charge_layout_five.get_charge_index_and_base().first == 6); + + charge_layout_five.increase_charge_index_by_one(); + CHECK(charge_layout_five.get_charge_index_and_base().first == 7); + charge_layout_five.increase_charge_index_by_one(); + CHECK(charge_layout_five.get_charge_index_and_base().first == 8); + charge_layout_five.increase_charge_index_by_one(); + CHECK(charge_layout_five.get_charge_index_and_base().first == 9); + charge_layout_five.increase_charge_index_by_one(); + CHECK(charge_layout_five.get_charge_index_and_base().first == 10); + charge_layout_five.increase_charge_index_by_one(); + CHECK(charge_layout_five.get_charge_index_and_base().first == 11); + charge_layout_five.increase_charge_index_by_one(); + CHECK(charge_layout_five.get_charge_index_and_base().first == 12); + charge_layout_five.increase_charge_index_by_one(); + CHECK(charge_layout_five.get_charge_index_and_base().first == 13); + + CHECK(charge_layout_five.get_charge_state({0, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout_five.get_charge_state({0, 2, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout_five.get_charge_state({4, 1, 1}) == sidb_charge_state::NEUTRAL); + } + + SECTION("Physical validity check, small distance, not all can be negatively charged anymore") + { + lyt.assign_cell_type({1, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({0, 2, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({0, 2, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, sidb_simulation_parameters{}}; + charge_layout.assign_charge_state({1, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout.assign_charge_state({0, 2, 0}, sidb_charge_state::NEGATIVE); + charge_layout.assign_charge_state({0, 2, 1}, sidb_charge_state::NEGATIVE); + + // closely arranged SiDBs cannot be all negatively charged + charge_layout.update_local_potential(); + charge_layout.recompute_system_energy(); + charge_layout.validity_check(); + CHECK(!charge_layout.is_physically_valid()); + } + + SECTION("apply external voltage at two cells") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{3, -0.32}; + + lyt_new.assign_cell_type({0, 0, 1}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 5, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout_new{lyt_new, + params, + sidb_charge_state::NEUTRAL, + {}, + {{{0, 0, 1}, -0.5}}}; + REQUIRE(!charge_layout_new.get_external_potentials().empty()); + CHECK(charge_layout_new.get_external_potentials().size() == 1); + CHECK(charge_layout_new.get_external_potentials().size() == 1); + + REQUIRE(charge_layout_new.get_local_potential({0, 0, 1}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({1, 3, 0}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({10, 5, 1}).has_value()); + + CHECK_THAT(charge_layout_new.get_local_potential({0, 0, 1}).value() + 0.5, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(charge_layout_new.get_local_potential({1, 3, 0}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + CHECK_THAT(charge_layout_new.get_local_potential({10, 5, 1}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + charge_layout_new.assign_all_charge_states(sidb_charge_state::POSITIVE); + charge_layout_new.update_after_charge_change(); + CHECK(charge_layout_new.get_charge_state({0, 0, 1}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout_new.get_charge_state({1, 3, 0}) == sidb_charge_state::POSITIVE); + CHECK(charge_layout_new.get_charge_state({10, 5, 1}) == sidb_charge_state::POSITIVE); + + CHECK(charge_layout_new.get_local_potential({0, 0, 1}).value() > -0.5); + CHECK(charge_layout_new.get_local_potential({1, 3, 0}).value() > -0.5); + CHECK(charge_layout_new.get_local_potential({10, 5, 1}).value() > -0.5); + + charge_layout_new.assign_all_charge_states(sidb_charge_state::NEUTRAL); + + charge_layout_new.assign_local_external_potential({{{0, 0, 1}, -0.1}}); + CHECK_THAT(charge_layout_new.get_local_potential({0, 0, 1}).value() + 0.1, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(charge_layout_new.get_local_potential({1, 3, 0}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + CHECK_THAT(charge_layout_new.get_local_potential({10, 5, 1}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + + charge_layout_new.assign_local_external_potential({{{0, 0, 1}, -0.5}, {{10, 5, 1}, -0.1}}); + charge_layout_new.assign_all_charge_states(sidb_charge_state::NEGATIVE); + charge_layout_new.update_after_charge_change(); + + CHECK(charge_layout_new.get_local_potential({0, 0, 1}).value() < -0.5); + CHECK(charge_layout_new.get_local_potential({10, 5, 1}).value() < -0.1); + CHECK(charge_layout_new.get_local_potential({1, 3, 0}).value() < 0); + } + + SECTION("apply homogenous external voltage to layout") + { + const sidb_simulation_parameters params{3, -0.32}; + lyt.assign_cell_type({0, 0, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({10, 5, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt, params, sidb_charge_state::NEUTRAL}; + CHECK(charge_layout.get_external_potentials().empty()); + charge_layout.assign_global_external_potential(-0.1); + CHECK(!charge_layout.get_external_potentials().empty()); + + REQUIRE(charge_layout.get_local_potential({0, 0, 1}).has_value()); + REQUIRE(charge_layout.get_local_potential({1, 3, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({10, 5, 1}).has_value()); + + CHECK_THAT(charge_layout.get_local_potential({0, 0, 1}).value() + 0.1, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(charge_layout.get_local_potential({1, 3, 0}).value() + 0.1, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(charge_layout.get_local_potential({10, 5, 1}).value() + 0.1, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + + lyt.assign_cell_type({0, 0, 1}, TestType::cell_type::EMPTY); + lyt.assign_cell_type({1, 3, 0}, TestType::cell_type::EMPTY); + lyt.assign_cell_type({10, 5, 1}, TestType::cell_type::EMPTY); + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + charge_distribution_surface charge_layout_new{lyt, params, sidb_charge_state::NEGATIVE}; + charge_layout_new.assign_charge_state({3, 0, 0}, sidb_charge_state::NEUTRAL); + charge_layout_new.assign_charge_state({5, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.update_after_charge_change(); + CHECK(charge_layout_new.is_physically_valid()); + + charge_layout_new.assign_global_external_potential(0.2); + CHECK(!charge_layout_new.is_physically_valid()); + + charge_layout_new.assign_charge_state({0, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.assign_charge_state({3, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.assign_charge_state({5, 0, 0}, sidb_charge_state::NEGATIVE); + charge_layout_new.update_after_charge_change(); + CHECK(charge_layout_new.is_physically_valid()); + } + + SECTION("no external voltage given") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{3, -0.32}; + + lyt_new.assign_cell_type({0, 0, 1}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 5, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout_new{lyt_new, params, sidb_charge_state::NEUTRAL}; + + REQUIRE(charge_layout_new.get_local_potential({0, 0, 1}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({1, 3, 0}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({10, 5, 1}).has_value()); + + CHECK_THAT(charge_layout_new.get_local_potential({0, 0, 1}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + CHECK_THAT(charge_layout_new.get_local_potential({1, 3, 0}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + CHECK_THAT(charge_layout_new.get_local_potential({10, 5, 1}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + } + + SECTION("assign defect | negative defect") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{3, -0.32}; + + lyt_new.assign_cell_type({0, 0, 1}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 5, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout_new{lyt_new, params, sidb_charge_state::NEUTRAL, {}}; + + charge_layout_new.add_sidb_defect_to_potential_landscape( + {5, 1, 1}, sidb_defect{sidb_defect_type::UNKNOWN, -1, charge_layout_new.get_phys_params().epsilon_r, + charge_layout_new.get_phys_params().lambda_tf}); + + REQUIRE(charge_layout_new.get_local_potential({0, 0, 1}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({1, 3, 0}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({10, 5, 1}).has_value()); + + CHECK(charge_layout_new.get_local_potential({0, 0, 1}).value() < 0); + CHECK(charge_layout_new.get_local_potential({1, 3, 0}).value() < 0); + CHECK(charge_layout_new.get_local_potential({10, 5, 1}).value() < 0); + } + + SECTION("assign defect | positive defect") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{3, -0.32}; + + lyt_new.assign_cell_type({0, 0, 1}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({1, 3, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 5, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout_new{lyt_new, params, sidb_charge_state::NEUTRAL, {}}; + + charge_layout_new.add_sidb_defect_to_potential_landscape( + {5, 1, 1}, sidb_defect{sidb_defect_type::UNKNOWN, 1, charge_layout_new.get_phys_params().epsilon_r, + charge_layout_new.get_phys_params().lambda_tf}); + + REQUIRE(charge_layout_new.get_local_potential({0, 0, 1}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({1, 3, 0}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({10, 5, 1}).has_value()); + + CHECK(charge_layout_new.get_local_potential({0, 0, 1}).value() > 0); + CHECK(charge_layout_new.get_local_potential({1, 3, 0}).value() > 0); + CHECK(charge_layout_new.get_local_potential({10, 5, 1}).value() > 0); + } + + SECTION("assign defect and perturber") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{3, -0.32}; + + lyt_new.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 5, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout_new{lyt_new, params, sidb_charge_state::NEUTRAL, {}}; + + REQUIRE(charge_layout_new.get_local_potential({0, 0, 0}).has_value()); + REQUIRE(charge_layout_new.get_local_potential({10, 5, 1}).has_value()); + + charge_layout_new.assign_charge_state({10, 5, 1}, sidb_charge_state::NEGATIVE); + charge_layout_new.add_sidb_defect_to_potential_landscape( + {-10, 5, 1}, sidb_defect{sidb_defect_type::UNKNOWN, 1, charge_layout_new.get_phys_params().epsilon_r, + charge_layout_new.get_phys_params().lambda_tf}); + CHECK_THAT(charge_layout_new.get_local_potential({0, 0, 0}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + } + + SECTION("layout with perturber |assigning and erasing defect") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{3, -0.32}; + + lyt_new.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 5, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt_new, params, sidb_charge_state::NEUTRAL, {}}; + + REQUIRE(charge_layout.get_local_potential({0, 0, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({10, 5, 1}).has_value()); + + charge_layout.assign_charge_state({10, 5, 1}, sidb_charge_state::NEGATIVE); + charge_layout.update_after_charge_change(); + charge_layout.add_sidb_defect_to_potential_landscape( + {-10, 5, 1}, sidb_defect{sidb_defect_type::UNKNOWN, 1, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf}); + + CHECK_THAT(charge_layout.get_local_potential({0, 0, 0}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + + charge_layout.erase_defect({-10, 5, 1}); + CHECK(charge_layout.get_local_potential({0, 0, 0}).value() < 0); + } + + SECTION("layout with neutrally charged SiDBs |assigning and erasing defect") + { + TestType lyt_new{{11, 11}}; + const sidb_simulation_parameters params{3, -0.32}; + + lyt_new.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt_new.assign_cell_type({10, 5, 1}, TestType::cell_type::NORMAL); + + charge_distribution_surface charge_layout{lyt_new, params, sidb_charge_state::NEUTRAL}; + charge_layout.add_sidb_defect_to_potential_landscape( + {-10, 5, 1}, sidb_defect{sidb_defect_type::UNKNOWN, 1, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf}); + + REQUIRE(charge_layout.get_local_potential({0, 0, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({10, 5, 1}).has_value()); + + CHECK(charge_layout.get_local_potential({0, 0, 0}).value() > 0); + CHECK(charge_layout.get_local_potential({10, 5, 1}).value() > 0); + + charge_layout.erase_defect({-10, 5, 1}); + CHECK_THAT(charge_layout.get_local_potential({0, 0, 0}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + CHECK_THAT(charge_layout.get_local_potential({10, 5, 1}).value(), + Catch::Matchers::WithinAbs(0.000000, 0.000001)); + } + + SECTION("experiments with defects") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{2, -0.32}; + + charge_distribution_surface charge_layout{lyt, params, sidb_charge_state::NEGATIVE, {0, 0, 0}}; + + REQUIRE(charge_layout.get_local_potential({0, 0, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({3, 0, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({5, 0, 0}).has_value()); + + CHECK(charge_layout.get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({3, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 0, 0}) == sidb_charge_state::NEGATIVE); + auto loc_one_wo_defect = charge_layout.get_local_potential({0, 0, 0}).value(); + auto loc_two_wo_defect = charge_layout.get_local_potential({3, 0, 0}).value(); + auto loc_three_wo_defect = charge_layout.get_local_potential({5, 0, 0}).value(); + + charge_layout.add_sidb_defect_to_potential_landscape( + {-4, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -1, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf}); + auto loc_one_w_negative_defect = charge_layout.get_local_potential({0, 0, 0}).value(); + auto loc_two_w_negative_defect = charge_layout.get_local_potential({3, 0, 0}).value(); + auto loc_three_w_negative_defect = charge_layout.get_local_potential({5, 0, 0}).value(); + auto defect_potentials_negative = charge_layout.get_defect_potentials(); + REQUIRE(!defect_potentials_negative.empty()); + + CHECK(loc_one_wo_defect > loc_one_w_negative_defect); + CHECK(loc_two_wo_defect > loc_two_w_negative_defect); + CHECK(loc_three_wo_defect > loc_three_w_negative_defect); + + charge_layout.add_sidb_defect_to_potential_landscape( + {-4, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, 0, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf}); + auto loc_one_w_neutral_defect = charge_layout.get_local_potential({0, 0, 0}).value(); + auto loc_two_w_neutral_defect = charge_layout.get_local_potential({3, 0, 0}).value(); + auto loc_three_w_neutral_defect = charge_layout.get_local_potential({5, 0, 0}).value(); + CHECK_THAT(loc_one_wo_defect - loc_one_w_neutral_defect, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(loc_two_wo_defect - loc_two_w_neutral_defect, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(loc_three_wo_defect - loc_three_w_neutral_defect, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + + charge_layout.add_sidb_defect_to_potential_landscape( + {-4, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, 1, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf}); + auto loc_one_w_positive_defect = charge_layout.get_local_potential({0, 0, 0}).value(); + auto loc_two_w_positive_defect = charge_layout.get_local_potential({3, 0, 0}).value(); + auto loc_three_w_positive_defect = charge_layout.get_local_potential({5, 0, 0}).value(); + auto defect_potentials_positive = charge_layout.get_defect_potentials(); + REQUIRE(!defect_potentials_positive.empty()); + + CHECK(loc_one_w_positive_defect > loc_one_w_neutral_defect); + CHECK(loc_two_w_positive_defect > loc_two_w_neutral_defect); + CHECK(loc_three_w_positive_defect > loc_three_w_neutral_defect); + + CHECK_THAT((defect_potentials_negative[{0, 0, 0}] + defect_potentials_positive[{0, 0, 0}]), + Catch::Matchers::WithinAbs(0.0, 0.000001)); + CHECK_THAT((defect_potentials_negative[{3, 0, 0}] + defect_potentials_positive[{3, 0, 0}]), + Catch::Matchers::WithinAbs(0.0, 0.000001)); + CHECK_THAT((defect_potentials_negative[{5, 0, 0}] + defect_potentials_positive[{5, 0, 0}]), + Catch::Matchers::WithinAbs(0.0, 0.000001)); + } + + SECTION("experiments with defects | assigning and reassigning defects") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{2, -0.32}; + + charge_distribution_surface charge_layout{lyt, params, sidb_charge_state::NEGATIVE, {0, 0, 0}}; + + REQUIRE(charge_layout.get_local_potential({0, 0, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({3, 0, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({5, 0, 0}).has_value()); + + CHECK(charge_layout.get_charge_state({0, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({3, 0, 0}) == sidb_charge_state::NEGATIVE); + CHECK(charge_layout.get_charge_state({5, 0, 0}) == sidb_charge_state::NEGATIVE); + auto loc_one_wo_defect = charge_layout.get_local_potential({0, 0, 0}).value(); + auto loc_two_wo_defect = charge_layout.get_local_potential({3, 0, 0}).value(); + auto loc_three_wo_defect = charge_layout.get_local_potential({5, 0, 0}).value(); + + charge_layout.add_sidb_defect_to_potential_landscape( + {-4, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -1, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf}); + auto loc_one_w_negative_defect = charge_layout.get_local_potential({0, 0, 0}).value(); + auto loc_two_w_negative_defect = charge_layout.get_local_potential({3, 0, 0}).value(); + auto loc_three_w_negative_defect = charge_layout.get_local_potential({5, 0, 0}).value(); + auto defect_potentials_negative = charge_layout.get_defect_potentials(); + REQUIRE(!defect_potentials_negative.empty()); + + CHECK(loc_one_wo_defect > loc_one_w_negative_defect); + CHECK(loc_two_wo_defect > loc_two_w_negative_defect); + CHECK(loc_three_wo_defect > loc_three_w_negative_defect); + + charge_layout.add_sidb_defect_to_potential_landscape( + {-4, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, 0, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf}); + auto loc_one_w_neutral_defect = charge_layout.get_local_potential({0, 0, 0}).value(); + auto loc_two_w_neutral_defect = charge_layout.get_local_potential({3, 0, 0}).value(); + auto loc_three_w_neutral_defect = charge_layout.get_local_potential({5, 0, 0}).value(); + + CHECK_THAT(loc_one_wo_defect - loc_one_w_neutral_defect, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(loc_two_wo_defect - loc_two_w_neutral_defect, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(loc_three_wo_defect - loc_three_w_neutral_defect, + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + + charge_layout.add_sidb_defect_to_potential_landscape( + {-4, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, 1, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf}); + auto loc_one_w_positive_defect = charge_layout.get_local_potential({0, 0, 0}).value(); + auto loc_two_w_positive_defect = charge_layout.get_local_potential({3, 0, 0}).value(); + auto loc_three_w_positive_defect = charge_layout.get_local_potential({5, 0, 0}).value(); + auto defect_potentials_positive = charge_layout.get_defect_potentials(); + REQUIRE(!defect_potentials_positive.empty()); + + CHECK(loc_one_w_positive_defect > loc_one_w_neutral_defect); + CHECK(loc_two_w_positive_defect > loc_two_w_neutral_defect); + CHECK(loc_three_w_positive_defect > loc_three_w_neutral_defect); + + CHECK_THAT((defect_potentials_negative[{0, 0, 0}] + defect_potentials_positive[{0, 0, 0}]), + Catch::Matchers::WithinAbs(0.0, 0.000001)); + CHECK_THAT((defect_potentials_negative[{3, 0, 0}] + defect_potentials_positive[{3, 0, 0}]), + Catch::Matchers::WithinAbs(0.0, 0.000001)); + CHECK_THAT((defect_potentials_negative[{5, 0, 0}] + defect_potentials_positive[{5, 0, 0}]), + Catch::Matchers::WithinAbs(0.0, 0.000001)); + } + + SECTION("assign defect on DB position which is not allowed") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{2, -0.32}; + + charge_distribution_surface charge_layout{lyt, params, sidb_charge_state::NEUTRAL, {0, 0, 0}}; + + REQUIRE(charge_layout.get_local_potential({0, 0, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({3, 0, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({5, 0, 0}).has_value()); + + CHECK(charge_layout.get_charge_state({0, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_state({3, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_state({5, 0, 0}) == sidb_charge_state::NEUTRAL); + auto loc_one_wo_defect = charge_layout.get_local_potential({0, 0, 0}).value(); + auto loc_two_wo_defect = charge_layout.get_local_potential({3, 0, 0}).value(); + auto loc_three_wo_defect = charge_layout.get_local_potential({5, 0, 0}).value(); + + charge_layout.add_sidb_defect_to_potential_landscape( + {0, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -1, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf}); + + CHECK_THAT(loc_one_wo_defect - (charge_layout.get_local_potential({0, 0, 0}).value()), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(loc_two_wo_defect - (charge_layout.get_local_potential({3, 0, 0}).value()), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(loc_three_wo_defect - (charge_layout.get_local_potential({5, 0, 0}).value()), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + + auto defect_potentials_negative = charge_layout.get_defect_potentials(); + CHECK(defect_potentials_negative.empty()); + } + + SECTION("assign defects with different screening lengths") + { + lyt.assign_cell_type({0, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({3, 0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 0, 0}, TestType::cell_type::NORMAL); + + const sidb_simulation_parameters params{2, -0.32}; + + charge_distribution_surface charge_layout{lyt, params, sidb_charge_state::NEUTRAL, {0, 0, 0}}; + + REQUIRE(charge_layout.get_local_potential({0, 0, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({3, 0, 0}).has_value()); + REQUIRE(charge_layout.get_local_potential({5, 0, 0}).has_value()); + + CHECK(charge_layout.get_charge_state({0, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_state({3, 0, 0}) == sidb_charge_state::NEUTRAL); + CHECK(charge_layout.get_charge_state({5, 0, 0}) == sidb_charge_state::NEUTRAL); + + charge_layout.add_sidb_defect_to_potential_landscape( + {8, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -1, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf}); + + auto loc_one_w_defect_normal_screening = charge_layout.get_local_potential({0, 0, 0}).value(); + auto loc_two_w_defect_normal_screening = charge_layout.get_local_potential({3, 0, 0}).value(); + auto loc_three_w_defect_normal_screening = charge_layout.get_local_potential({5, 0, 0}).value(); + + charge_layout.add_sidb_defect_to_potential_landscape( + {8, 0, 0}, sidb_defect{sidb_defect_type::UNKNOWN, -1, charge_layout.get_phys_params().epsilon_r, + charge_layout.get_phys_params().lambda_tf * 20}); + + auto loc_one_w_defec_strong_screening = charge_layout.get_local_potential({0, 0, 0}).value(); + auto loc_two_w_defect_strong_screening = charge_layout.get_local_potential({3, 0, 0}).value(); + auto loc_three_w_defect_strong_screening = charge_layout.get_local_potential({5, 0, 0}).value(); + + auto defect_potentials_negative = charge_layout.get_defect_potentials(); + CHECK(!defect_potentials_negative.empty()); + + CHECK(loc_one_w_defect_normal_screening > loc_one_w_defec_strong_screening); + CHECK(loc_two_w_defect_normal_screening > loc_two_w_defect_strong_screening); + CHECK(loc_three_w_defect_normal_screening > loc_three_w_defect_strong_screening); + + charge_layout.erase_defect({8, 0, 0}); + CHECK_THAT(charge_layout.get_local_potential({0, 0, 0}).value(), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(charge_layout.get_local_potential({3, 0, 0}).value(), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + CHECK_THAT(charge_layout.get_local_potential({5, 0, 0}).value(), + Catch::Matchers::WithinAbs(0, physical_constants::POP_STABILITY_ERR)); + } } diff --git a/test/technology/sidb_defects.cpp b/test/technology/sidb_defects.cpp index 577159b7f..b83e1209a 100644 --- a/test/technology/sidb_defects.cpp +++ b/test/technology/sidb_defects.cpp @@ -84,7 +84,6 @@ TEST_CASE("Test for units", "[sidb-defects]") CHECK(defect_three.epsilon_r == 5); CHECK(defect_three.lambda_tf == 0.0); } - TEST_CASE("Compare Defect", "[sidb-defects]") { SECTION("Different types")