From b2132925b68db524d09377774c503204aa4042ae Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Wed, 21 Aug 2024 10:02:50 +0200 Subject: [PATCH] :sparkles: Multi-dimensional operational domain computation (#493) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: Added I/O coloring to SiDB layout printing * :art: Changed the default operational/non-operational tags to `1` and `0` * :art: Small changes * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :poop: Save commit, stuff doesn't work yet * :sparkles: Exposed `bdl_input_iterator` in pyfiction * :memo: Added RST documentation * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :thread: First attempt to port grid search from execution policies to manual threading * :thread: Rewrite `random_sampling` in `operational_domain.hpp` to use `std::thread`s instead of execution policies * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :art: Reduced code duplication; more work can be done here in `grid_search` * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :sparkles: Introduce n-dimensional operational domain computation (needs testing) * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :art: Small code clean up * :sparkles: Added 3D Moore neighborhood * :white_check_mark: Added test cases for 3-dimensional operational domain computation * :sparkles: Support n-dimensional operational domains in `write_operational_domain` * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :sparkles: Support n-dimensional operational domains in experiments * :sparkles: Added Bestagon experiment script * :sparkles: Enabled Contour Tracing-based operational domain computation to work on operational domains that possess multiple islands * :alembic: Added an operational domain computation experiment script for the Bestagon gate library * :alembic: Added 3D versions of the operational domain computation experiment scripts and added total runtime tracking * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :sparkles: Added a flag to `write_operational_domain` to skip the writing of non-operational samples to reduce the resulting CSV file * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :zap: Performance improvements by reserving vector space prior to assignment * :art: Minor consistency fixes * :alembic: Small 3D operational domain experiment adjustments * :alembic: Increased number of random samples * :alembic: Increased resolution for 3D operational domain experiments * :zap: Improved load-balancing in multithreaded grid search-based operational domain computation, which increases performance * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :pencil2: Fixed a typo * :memo: Fixed a docstring ambiguity * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :twisted_rightwards_arrows: conduct merge. Python bindings are not updated yet. * 🎨 Incorporated pre-commit fixes * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :twisted_rightwards_arrows: adjust some python APIs * :art: small adjustments after merge. * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :arrow_up: use submodule versions from main. * 🎨 Incorporated pre-commit fixes * :art: small fix of experiments after merge. * :snake: adjust python binding after merge * :bug: use mutex to avoid race conditions. * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :thread: use multithreading for grid_search_for_physically_valid_parameters * :memo: fix docu. * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :memo: small fix. * :memo: fix missing doc-label. * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :white_check_mark: unit test for math utils function. * :white_check_mark: add unit tests. * :white_check_mark: add test for parameter point. * :art: Improved consistency in naming, docstrings, formatting, etc. * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :art: Remove unnecessary lock guards * :art: Remove unnecessary lock guards * :thread: Thread-safe value-retrieval from std::atomic types * :art: Minor consistency fixes * :thread: Switched std::mutex to std::shared_mutex for parallel read access * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :thread: Removed the need for manual mutexes and locks in operational domain computation * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :children_crossing: Added sweep parameter validity checks * :bug: Fixed operational domain z-dimension handling in the CLI's `opdom` command * :bug: Fixed command `tt`'s truth table string parsing * :memo: Added documentation on the new `opdom` CLI functionality * :memo: Updated the operational domain docstrings for flood fill and contour tracing in accordance with the latest changes * :children_crossing: Added checks for step size values (negative, 0) to operational domain computation * :memo: Clarified docstring * :memo: Fixed a documentation copy-paste bug * :art: `const`ness and include cleanup * :memo: Fixed header date in experiment script * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :bug: Fixed return value of `operational_domain::get_value` * :snake: Updated pyfiction docstrings with regard to operational domain computation changes * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :memo: Fixed Python documentation for `bdl_input_iterator` * :art: Removed unused include * :art: Moved `contains_parameter_point` and `find_key_with_tolerance` to the `detail` namespace * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :bug: Added missing forward-declaration to fix compiler error * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :bug: Fixed compiler error in `determine_physically_valid_parameters` * :art: Incorporated Jan's feedback * :art: Restructured code in an effort to circumvent the alice bug that doesn't reset the CLI parameters properly * :memo: Mentioned CLI bug in docstring * :memo: Update pyfiction docstrings Signed-off-by: GitHub Actions * :snake: Added `sample_writing_mode` to `write_operational_domain`'s Python bindings --------- Signed-off-by: GitHub Actions Co-authored-by: GitHub Actions Co-authored-by: Drewniok Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jan Drewniok <97012901+Drewniok@users.noreply.github.com> --- .../algorithms/iter/bdl_input_iterator.hpp | 113 ++ .../simulation/sidb/detect_bdl_pairs.hpp | 9 +- .../determine_physically_valid_parameters.hpp | 8 +- .../simulation/sidb/operational_domain.hpp | 102 +- .../inout/write_operational_domain.hpp | 11 + .../pyfiction/pybind11_mkdoc_docstrings.hpp | 479 ++++--- bindings/pyfiction/pyfiction.cpp | 11 +- .../test/algorithms/iter/__init__.py | 16 + .../algorithms/iter/bdl_input_iterator.py | 182 +++ ...t_determine_physically_valid_parameters.py | 12 +- .../sidb/test_operational_domain.py | 22 +- .../simulation/sidb/test_time_to_solution.py | 2 +- cli/cmd/io/tt.hpp | 10 +- cli/cmd/simulation/opdom.hpp | 293 +++-- docs/algorithms/iterators.rst | 16 +- docs/algorithms/sidb_simulation.rst | 4 +- docs/cli.rst | 16 +- docs/utils/utils.rst | 2 + .../operational_domain_3d_bestagon.cpp | 211 +++ .../operational_domain_3d_siqad.cpp | 205 +++ .../operational_domain_bestagon.cpp | 232 ++++ .../operational_domain_siqad.cpp | 97 +- .../algorithms/iter/bdl_input_iterator.hpp | 14 +- .../determine_physically_valid_parameters.hpp | 11 +- .../sidb/displacement_robustness_domain.hpp | 34 +- .../simulation/sidb/is_operational.hpp | 7 +- .../simulation/sidb/operational_domain.hpp | 1159 +++++++++++------ include/fiction/io/print_layout.hpp | 17 +- .../fiction/io/write_operational_domain.hpp | 99 +- include/fiction/utils/hash.hpp | 1 + include/fiction/utils/layout_utils.hpp | 1 + include/fiction/utils/math_utils.hpp | 53 +- include/fiction/utils/phmap_utils.hpp | 1 + test/algorithms/iter/bdl_input_iterator.cpp | 16 +- test/algorithms/mockturtle.cpp | 3 +- .../simulation/sidb/critical_temperature.cpp | 5 +- .../determine_physically_valid_parameters.cpp | 160 ++- .../simulation/sidb/operational_domain.cpp | 565 ++++++-- test/io/write_operational_domain.cpp | 82 +- test/utils/math_utils.cpp | 149 +++ 40 files changed, 3401 insertions(+), 1029 deletions(-) create mode 100644 bindings/pyfiction/include/pyfiction/algorithms/iter/bdl_input_iterator.hpp create mode 100644 bindings/pyfiction/test/algorithms/iter/__init__.py create mode 100644 bindings/pyfiction/test/algorithms/iter/bdl_input_iterator.py create mode 100644 experiments/operational_domain/operational_domain_3d_bestagon.cpp create mode 100644 experiments/operational_domain/operational_domain_3d_siqad.cpp create mode 100644 experiments/operational_domain/operational_domain_bestagon.cpp diff --git a/bindings/pyfiction/include/pyfiction/algorithms/iter/bdl_input_iterator.hpp b/bindings/pyfiction/include/pyfiction/algorithms/iter/bdl_input_iterator.hpp new file mode 100644 index 000000000..45dbe7fda --- /dev/null +++ b/bindings/pyfiction/include/pyfiction/algorithms/iter/bdl_input_iterator.hpp @@ -0,0 +1,113 @@ +// +// Created by marcel on 19.05.24. +// + +#ifndef PYFICTION_BDL_INPUT_ITERATOR_HPP +#define PYFICTION_BDL_INPUT_ITERATOR_HPP + +#include "pyfiction/documentation.hpp" +#include "pyfiction/types.hpp" + +#include +#include + +#include +#include +#include + +#include +#include + +namespace pyfiction +{ + +namespace detail +{ + +template +void bdl_input_iterator(pybind11::module& m, const std::string& lattice) +{ + namespace py = pybind11; + using namespace py::literals; + + py::class_>(m, fmt::format("bdl_input_iterator_{}", lattice).c_str(), + DOC(fiction_bdl_input_iterator)) + .def(py::init(), "lyt"_a, + "params"_a = fiction::detect_bdl_pairs_params{}, DOC(fiction_bdl_input_iterator_bdl_input_iterator)) + .def( + "__next__", + [](fiction::bdl_input_iterator& self) -> Lyt& + { + if (self >= ((1ull << self.num_input_pairs()) - 1)) + { + throw py::stop_iteration(); + } + + auto result = *self; + ++self; + return result; + }, + DOC(fiction_bdl_input_iterator_operator_mul)) + .def( + "__eq__", [](const fiction::bdl_input_iterator& self, const uint64_t m) -> bool { return self == m; }, + "m"_a, DOC(fiction_bdl_input_iterator_operator_eq)) + .def( + "__ne__", [](const fiction::bdl_input_iterator& self, const uint64_t m) -> bool { return self != m; }, + "m"_a, DOC(fiction_bdl_input_iterator_operator_ne)) + .def( + "__lt__", [](const fiction::bdl_input_iterator& self, const uint64_t m) -> bool { return self < m; }, + "m"_a, DOC(fiction_bdl_input_iterator_operator_lt)) + .def( + "__le__", [](const fiction::bdl_input_iterator& self, const uint64_t m) -> bool { return self <= m; }, + "m"_a, DOC(fiction_bdl_input_iterator_operator_le)) + .def( + "__gt__", [](const fiction::bdl_input_iterator& self, const uint64_t m) -> bool { return self > m; }, + "m"_a, DOC(fiction_bdl_input_iterator_operator_gt)) + .def( + "__ge__", [](const fiction::bdl_input_iterator& self, const uint64_t m) -> bool { return self >= m; }, + "m"_a, DOC(fiction_bdl_input_iterator_operator_ge)) + .def( + "__add__", [](const fiction::bdl_input_iterator& self, const int m) -> fiction::bdl_input_iterator + { return self + m; }, "m"_a, DOC(fiction_bdl_input_iterator_operator_add)) + .def( + "__iadd__", + [](fiction::bdl_input_iterator& self, const int m) -> fiction::bdl_input_iterator& + { + self += m; + return self; + }, + "m"_a, DOC(fiction_bdl_input_iterator_operator_iadd)) + .def( + "__sub__", [](const fiction::bdl_input_iterator& self, const int m) { return self - m; }, "m"_a, + DOC(fiction_bdl_input_iterator_operator_sub)) + .def( + "__isub__", + [](fiction::bdl_input_iterator& self, const int m) -> fiction::bdl_input_iterator& + { + self -= m; + return self; + }, + "m"_a, DOC(fiction_bdl_input_iterator_operator_isub)) + .def( + "__getitem__", [](const fiction::bdl_input_iterator& self, int m) -> fiction::bdl_input_iterator + { return self[m]; }, "m"_a, DOC(fiction_bdl_input_iterator_operator_array)) + + .def("num_input_pairs", &fiction::bdl_input_iterator::num_input_pairs) + .def("get_layout", [](const fiction::bdl_input_iterator& self) -> const Lyt& { return *self; }) + + ; +} + +} // namespace detail + +inline void bdl_input_iterator(pybind11::module& m) +{ + // NOTE be careful with the order of the following calls! Python will resolve the first matching overload! + + detail::bdl_input_iterator(m, "100"); + detail::bdl_input_iterator(m, "111"); +} + +} // namespace pyfiction + +#endif // PYFICTION_BDL_INPUT_ITERATOR_HPP diff --git a/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/detect_bdl_pairs.hpp b/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/detect_bdl_pairs.hpp index b9c61c1b7..2a43ec03a 100644 --- a/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/detect_bdl_pairs.hpp +++ b/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/detect_bdl_pairs.hpp @@ -9,10 +9,15 @@ #include "pyfiction/types.hpp" #include +#include +#include +#include #include #include +#include + namespace pyfiction { @@ -49,7 +54,9 @@ inline void detect_bdl_pairs(pybind11::module& m) .def_readwrite("minimum_distance", &fiction::detect_bdl_pairs_params::minimum_distance, DOC(fiction_detect_bdl_pairs_params_minimum_distance)) .def_readwrite("maximum_distance", &fiction::detect_bdl_pairs_params::maximum_distance, - DOC(fiction_detect_bdl_pairs_params_maximum_distance)); + DOC(fiction_detect_bdl_pairs_params_maximum_distance)) + + ; // NOTE be careful with the order of the following calls! Python will resolve the first matching overload! diff --git a/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/determine_physically_valid_parameters.hpp b/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/determine_physically_valid_parameters.hpp index e1fca7e3d..0cff96e1c 100644 --- a/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/determine_physically_valid_parameters.hpp +++ b/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/determine_physically_valid_parameters.hpp @@ -38,13 +38,9 @@ inline void determine_physically_valid_parameters(pybind11::module& m) py::class_>(m, "physically_valid_parameters_domain", DOC(fiction_operational_domain)) + // todo add docu .def(py::init<>()) - .def_readwrite("x_dimension_parameter", - &fiction::operational_domain::x_dimension, - DOC(fiction_operational_domain_x_dimension)) - .def_readwrite("y_dimension_parameter", - &fiction::operational_domain::y_dimension, - DOC(fiction_operational_domain_y_dimension)) + .def_readwrite("dimensions", &fiction::operational_domain::dimensions) .def( "get_excited_state_number_for_parameter", diff --git a/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/operational_domain.hpp b/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/operational_domain.hpp index 59681800c..3a85fae52 100644 --- a/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/operational_domain.hpp +++ b/bindings/pyfiction/include/pyfiction/algorithms/simulation/sidb/operational_domain.hpp @@ -8,13 +8,14 @@ #include "pyfiction/documentation.hpp" #include "pyfiction/types.hpp" +#include #include #include #include #include -#include +#include namespace pyfiction { @@ -51,18 +52,10 @@ inline void operational_domain(pybind11::module& m) namespace py = pybind11; using namespace pybind11::literals; - py::enum_(m, "sweep_parameter", DOC(fiction_sweep_parameter)) - .value("EPSILON_R", fiction::sweep_parameter::EPSILON_R, DOC(fiction_sweep_parameter_EPSILON_R)) - .value("LAMBDA_TF", fiction::sweep_parameter::LAMBDA_TF, DOC(fiction_sweep_parameter_LAMBDA_TF)) - .value("MU_MINUS", fiction::sweep_parameter::MU_MINUS, DOC(fiction_sweep_parameter_MU_MINUS)) - - ; - py::class_(m, "parameter_point", DOC(fiction_parameter_point)) - .def(py::init<>()) - .def(py::init(), "x_val"_a, "y_val"_a) - .def_readwrite("x", &fiction::parameter_point::x, DOC(fiction_parameter_point_x)) - .def_readwrite("y", &fiction::parameter_point::y, DOC(fiction_parameter_point_y)) + .def(py::init<>(), DOC(fiction_parameter_point_parameter_point)) + .def(py::init>(), "values"_a, DOC(fiction_parameter_point_parameter_point_2)) + .def_readwrite("parameters", &fiction::parameter_point::parameters, DOC(fiction_parameter_point)) .def(py::self == py::self, "other"_a, DOC(fiction_parameter_point_operator_eq)) .def(py::self != py::self, "other"_a, DOC(fiction_parameter_point_operator_ne)) @@ -72,19 +65,44 @@ inline void operational_domain(pybind11::module& m) ; + py::enum_(m, "sweep_parameter", DOC(fiction_sweep_parameter)) + .value("EPSILON_R", fiction::sweep_parameter::EPSILON_R, DOC(fiction_sweep_parameter_EPSILON_R)) + .value("LAMBDA_TF", fiction::sweep_parameter::LAMBDA_TF, DOC(fiction_sweep_parameter_LAMBDA_TF)) + .value("MU_MINUS", fiction::sweep_parameter::MU_MINUS, DOC(fiction_sweep_parameter_MU_MINUS)) + + ; + py::class_>( m, "operational_domain", DOC(fiction_operational_domain)) .def(py::init<>()) - .def_readwrite("x_dimension", - &fiction::operational_domain::x_dimension, - DOC(fiction_operational_domain_x_dimension)) - .def_readwrite("y_dimension", - &fiction::operational_domain::y_dimension, - DOC(fiction_operational_domain_y_dimension)) + .def_readwrite("dimensions", + &fiction::operational_domain::dimensions, + DOC(fiction_operational_domain_dimensions)) .def_readwrite( "operational_values", &fiction::operational_domain::operational_values, - DOC(fiction_operational_domain_operational_values)); + DOC(fiction_operational_domain_operational_values)) + + .def("get_value", + &fiction::operational_domain::get_value, "point"_a, + DOC(fiction_operational_domain_get_value)) + + ; + + py::class_(m, "operational_domain_value_range", + DOC(fiction_operational_domain_value_range)) + .def(py::init(), "dimension"_a) + .def(py::init(), "dimension"_a, "min"_a, "max"_a, "step"_a) + .def_readwrite("dimension", &fiction::operational_domain_value_range::dimension, + DOC(fiction_operational_domain_value_range_dimension)) + .def_readwrite("min", &fiction::operational_domain_value_range::min, + DOC(fiction_operational_domain_value_range_dimension)) + .def_readwrite("max", &fiction::operational_domain_value_range::max, + DOC(fiction_operational_domain_value_range_max)) + .def_readwrite("step", &fiction::operational_domain_value_range::step, + DOC(fiction_operational_domain_value_range_step)) + + ; py::class_(m, "operational_domain_params", DOC(fiction_operational_domain_params)) @@ -93,22 +111,8 @@ inline void operational_domain(pybind11::module& m) DOC(fiction_operational_domain_params_simulation_parameters)) .def_readwrite("sim_engine", &fiction::operational_domain_params::sim_engine, DOC(fiction_operational_domain_params_sim_engine)) - .def_readwrite("x_dimension", &fiction::operational_domain_params::x_dimension, - DOC(fiction_operational_domain_params_x_dimension)) - .def_readwrite("x_min", &fiction::operational_domain_params::x_min, - DOC(fiction_operational_domain_params_x_min)) - .def_readwrite("x_max", &fiction::operational_domain_params::x_max, - DOC(fiction_operational_domain_params_x_max)) - .def_readwrite("x_step", &fiction::operational_domain_params::x_step, - DOC(fiction_operational_domain_params_x_step)) - .def_readwrite("y_dimension", &fiction::operational_domain_params::y_dimension, - DOC(fiction_operational_domain_params_y_dimension)) - .def_readwrite("y_min", &fiction::operational_domain_params::y_min, - DOC(fiction_operational_domain_params_y_min)) - .def_readwrite("y_max", &fiction::operational_domain_params::y_max, - DOC(fiction_operational_domain_params_y_max)) - .def_readwrite("y_step", &fiction::operational_domain_params::y_step, - DOC(fiction_operational_domain_params_y_step)) + .def_readwrite("sweep_dimensions", &fiction::operational_domain_params::sweep_dimensions, + DOC(fiction_operational_domain_params_sweep_dimensions)) .def_readwrite("bdl_params", &fiction::operational_domain_params::bdl_params, DOC(fiction_operational_domain_params_bdl_params)) @@ -116,19 +120,21 @@ inline void operational_domain(pybind11::module& m) py::class_(m, "operational_domain_stats", DOC(fiction_operational_domain_stats)) .def(py::init<>()) - .def_readwrite("time_total", &fiction::operational_domain_stats::time_total, - DOC(fiction_operational_domain_stats_duration)) - .def_readwrite("num_simulator_invocations", &fiction::operational_domain_stats::num_simulator_invocations, - DOC(fiction_operational_domain_stats_num_simulator_invocations)) - .def_readwrite("num_evaluated_parameter_combinations", - &fiction::operational_domain_stats::num_evaluated_parameter_combinations, - DOC(fiction_operational_domain_stats_num_evaluated_parameter_combinations)) - .def_readwrite("num_operational_parameter_combinations", - &fiction::operational_domain_stats::num_operational_parameter_combinations, - DOC(fiction_operational_domain_stats_num_operational_parameter_combinations)) - .def_readwrite("num_non_operational_parameter_combinations", - &fiction::operational_domain_stats::num_non_operational_parameter_combinations, - DOC(fiction_operational_domain_stats_num_non_operational_parameter_combinations)); + .def_readonly("time_total", &fiction::operational_domain_stats::time_total, + DOC(fiction_operational_domain_stats_duration)) + .def_readonly("num_simulator_invocations", &fiction::operational_domain_stats::num_simulator_invocations, + DOC(fiction_operational_domain_stats_num_simulator_invocations)) + .def_readonly("num_evaluated_parameter_combinations", + &fiction::operational_domain_stats::num_evaluated_parameter_combinations, + DOC(fiction_operational_domain_stats_num_evaluated_parameter_combinations)) + .def_readonly("num_operational_parameter_combinations", + &fiction::operational_domain_stats::num_operational_parameter_combinations, + DOC(fiction_operational_domain_stats_num_operational_parameter_combinations)) + .def_readonly("num_non_operational_parameter_combinations", + &fiction::operational_domain_stats::num_non_operational_parameter_combinations, + DOC(fiction_operational_domain_stats_num_non_operational_parameter_combinations)) + + ; // NOTE be careful with the order of the following calls! Python will resolve the first matching overload! diff --git a/bindings/pyfiction/include/pyfiction/inout/write_operational_domain.hpp b/bindings/pyfiction/include/pyfiction/inout/write_operational_domain.hpp index dd21aa52e..5000afbd4 100644 --- a/bindings/pyfiction/include/pyfiction/inout/write_operational_domain.hpp +++ b/bindings/pyfiction/include/pyfiction/inout/write_operational_domain.hpp @@ -20,6 +20,15 @@ inline void write_operational_domain(pybind11::module& m) namespace py = pybind11; using namespace py::literals; + py::enum_( + m, "sample_writing_mode", DOC(fiction_write_operational_domain_params_sample_writing_mode)) + .value("ALL_SAMPLES", fiction::write_operational_domain_params::sample_writing_mode::ALL_SAMPLES, + DOC(fiction_write_operational_domain_params_sample_writing_mode_ALL_SAMPLES)) + .value("OPERATIONAL_ONLY", fiction::write_operational_domain_params::sample_writing_mode::OPERATIONAL_ONLY, + DOC(fiction_write_operational_domain_params_sample_writing_mode_OPERATIONAL_ONLY)) + + ; + py::class_(m, "write_operational_domain_params", DOC(fiction_write_operational_domain_params)) .def(py::init<>()) @@ -27,6 +36,8 @@ inline void write_operational_domain(pybind11::module& m) DOC(fiction_write_operational_domain_params_operational_tag)) .def_readwrite("non_operational_tag", &fiction::write_operational_domain_params::non_operational_tag, DOC(fiction_write_operational_domain_params_non_operational_tag)) + .def_readwrite("writing_mode", &fiction::write_operational_domain_params::writing_mode, + DOC(fiction_write_operational_domain_params_writing_mode)) ; diff --git a/bindings/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp b/bindings/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp index 4189a3ee5..3726feccb 100644 --- a/bindings/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp +++ b/bindings/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp @@ -530,19 +530,17 @@ static const char *__doc_fiction_bdl_input_iterator_current_input_index = R"doc(The current input index. There are :math:`2^n` possible input states for an :math:`n`-input BDL layout.)doc"; -static const char *__doc_fiction_bdl_input_iterator_get_number_of_inputs = +static const char *__doc_fiction_bdl_input_iterator_input_pairs = R"doc(The detected input BDL pairs.)doc"; + +static const char *__doc_fiction_bdl_input_iterator_layout = R"doc(The layout to iterate over.)doc"; + +static const char *__doc_fiction_bdl_input_iterator_num_input_pairs = R"doc(Returns the total number of input BDL pairs of the given SiDB gate layout. Returns: The number of input BDL pairs.)doc"; -static const char *__doc_fiction_bdl_input_iterator_input_pairs = R"doc(The detected input BDL pairs.)doc"; - -static const char *__doc_fiction_bdl_input_iterator_layout = R"doc(The layout to iterate over.)doc"; - -static const char *__doc_fiction_bdl_input_iterator_num_inputs = R"doc(The amount of input BDL pairs.)doc"; - static const char *__doc_fiction_bdl_input_iterator_operator_add = R"doc(Addition operator. Computes the input state of the current iterator plus the given integer. @@ -879,6 +877,26 @@ Parameter ``sim_params``: Physical parameters used to determine whether positively charged SiDBs can occur.)doc"; +static const char *__doc_fiction_cartesian_combinations = +R"doc(This function computes the Cartesian product of a list of vectors. +Each vector in the input list represents a dimension, and the function +produces all possible combinations where each combination consists of +one element from each dimension vector. + +Template parameter ``VectorDataType``: + The type of elements in the vectors. + +Parameter ``sets``: + The sets to compute the Cartesian product for. In this + implementation, a vector of vectors is utilized for efficiency. + Each inner vector represents one dimension. The function generates + combinations using one element from each dimension vector. + +Returns: + A vector of vectors, where each inner vector represents a + combination of elements, one from each dimension. The total number + of combinations is the product of the sizes of the input vectors.)doc"; + static const char *__doc_fiction_cartesian_layout = R"doc(A layout type that utilizes offset coordinates to represent a Cartesian grid. Its faces are organized in the following way: @@ -3961,6 +3979,27 @@ static const char *__doc_fiction_detail_connect_and_place = R"doc()doc"; static const char *__doc_fiction_detail_connect_and_place_2 = R"doc()doc"; +static const char *__doc_fiction_detail_contains_parameter_point = R"doc(Forward-declaration for `operational_domain`.)doc"; + +static const char *__doc_fiction_detail_contains_parameter_point_2 = +R"doc(This function checks for the containment of a parameter point, +specified by `key`, in the provided map `map`. If the parameter point +is found in the map, the associated `MapType::value_type` is returned. +Otherwise, `std::nullopt` is returned. + +Template parameter ``MapType``: + The type of the map containing parameter points as keys. + +Parameter ``map``: + The map in which to search for `key`. + +Parameter ``key``: + The parameter point to search for in `map`. + +Returns: + The associated `MapType::value_type` of `key` in `map`, or + `std::nullopt` if `key` is not contained in `map`.)doc"; + static const char *__doc_fiction_detail_convert_array = R"doc(Based on https://stackoverflow.com/questions/57756557/initializing-a- stdarray-with-a-constant-value)doc"; @@ -4327,21 +4366,6 @@ Parameter ``ps``: Parameter ``st``: Statistics related to the displacement robustness computation.)doc"; -static const char *__doc_fiction_detail_displacement_robustness_domain_impl_generate_all_possible_combinations_of_displacements = -R"doc(This is a helper function, which recursively generates combinations of -SiDB displacements for all SiDBs based on the provided vector of -displacement vectors. - -Parameter ``result``: - The vector to store the generated combinations. The first element - describes the SiDBs of the first displaced layout. - -Parameter ``current_combination``: - The current combination being constructed. - -Parameter ``cell_index``: - The current cell_index in the vector of displacement vectors.)doc"; - static const char *__doc_fiction_detail_displacement_robustness_domain_impl_generate_valid_displaced_sidb_layouts = R"doc(This function generates all SiDB layouts with displacements based on the original layout. It filters out layouts where two or more SiDBs @@ -5257,6 +5281,25 @@ static const char *__doc_fiction_detail_fanout_substitution_impl_ps = R"doc()doc static const char *__doc_fiction_detail_fanout_substitution_impl_run = R"doc()doc"; +static const char *__doc_fiction_detail_find_key_with_tolerance = +R"doc(This function searches for a floating-point value specified by the +`key` in the provided map `map`, applying a tolerance specified by +`fiction::physical_constants::POP_STABILITY_ERR`. Each key in the map +is compared to the specified key within this tolerance. + +Template parameter ``MapType``: + The type of the map containing parameter points as keys. + +Parameter ``map``: + The map containing parameter points as keys and associated values. + +Parameter ``key``: + The parameter point to search for in the map. + +Returns: + An iterator to the found parameter point in the map, or + `map.cend()` if not found.)doc"; + static const char *__doc_fiction_detail_fix_wires = R"doc(Utility function to move wires that cross over empty tiles down one layer. This can happen if the wiring of a gate is deleted. @@ -6372,7 +6415,7 @@ static const char *__doc_fiction_detail_operational_domain_impl_find_operational R"doc(Performs random sampling to find any operational parameter combination. This function is useful if a single starting point is required within the domain to expand from. This function returns the -step in x and y dimension of the first operational point found. If no +step in all dimensions of the first operational point found. If no operational parameter combination can be found within the given number of samples, the function returns `std::nullopt`. @@ -6402,15 +6445,15 @@ Parameter ``samples``: The (partial) operational domain of the layout.)doc"; static const char *__doc_fiction_detail_operational_domain_impl_generate_random_step_points = -R"doc(Generates (potentially repeating) random `step_points` in the stored -parameter range. The number of generated points is exactly equal to -`samples`. +R"doc(Generates unique random `step_points` in the stored parameter range. +The number of generated points is at most equal to `samples`. Parameter ``samples``: - Number of random `step_point`s to generate. + Maximum number of random `step_point`s to generate. Returns: - A set of random `step_point`s in the stored parameter range.)doc"; + A vector of unique random `step_point`s in the stored parameter + range of size at most equal to `samples`.)doc"; static const char *__doc_fiction_detail_operational_domain_impl_grid_search = R"doc(Performs a grid search over the specified parameter ranges with the @@ -6435,26 +6478,69 @@ Parameter ``lyt``: number.)doc"; static const char *__doc_fiction_detail_operational_domain_impl_has_already_been_sampled = -R"doc(Determines whether the point at step position `(x, y)` has already -been sampled and returns the operational value at `(x, y)` if it -already exists. Here, `x` and `y` represent steps in the x and y -dimension, respectively, not the actual values of the parameters. +R"doc(Determines whether the point at step position `(d1, ..., dn)` has +already been sampled and returns the operational value at `(d1, ..., +dn)` if it already exists. Here, `di` represents steps in the i-th +dimension, not the actual values of the parameters. Parameter ``sp``: Step point to check. Returns: - The operational status of the point at step position `sp = (x, y)` - or `std::nullopt` if `(x, y)` has not been sampled yet.)doc"; + The operational status of the point at step position `sp = (d1, + ..., dn)` or `std::nullopt` if the point `(d1, ..., dn)` has not + been sampled yet.)doc"; + +static const char *__doc_fiction_detail_operational_domain_impl_indices = R"doc(Dimension steps.)doc"; + +static const char *__doc_fiction_detail_operational_domain_impl_infer_operational_status_in_enclosing_contour = +R"doc(Given a starting point, this function marks all points that are +enclosed by the operational domain contour as 'inferred operational'. +This assumes that the operational domain does not have holes. To the +best of the author's knowledge, at the time of writing this code, +there exists no proof that operational domains are always continuous, +i.e., hole-free. Marking points as 'inferred operational' can be +useful to avoid recomputation in, e.g., contour tracing if an +operational domain with multiple islands is investigated. + +The function starts at the given starting point and performs flood +fill to mark all points that are reachable from the starting point +until it encounters the non-operational edges. + +Note that no physical simulation is conducted by this function! + +Parameter ``starting_point``: + Step point at which to start the inference. If `starting_point` is + non-operational, this function might invoke undefined behavior.)doc"; + +static const char *__doc_fiction_detail_operational_domain_impl_inferred_op_domain = +R"doc(All the points inferred (assumed) to be operational but not actually +simulated.)doc"; + +static const char *__doc_fiction_detail_operational_domain_impl_is_step_point_inferred_operational = +R"doc(Checks whether the given step point is part of the inferred +operational domain. If it is, the point is marked as enclosed in the +operational domain. No simulation is performed on `sp`. If `sp` is not +contained in the inferred operational domain, it does not mean that +`sp` is definitely non-operational. It could still appear in the +regular operational domain with either status. + +This function is used by the contour tracing algorithm. + +Parameter ``sp``: + Step point to check for inferred operational status. + +Returns: + `true` iff `sp` is contained in `inferred_op_domain`.)doc"; static const char *__doc_fiction_detail_operational_domain_impl_is_step_point_operational = -R"doc(Logs and returns the operational status at the given point `sp = (x, -y)`. If the point has already been sampled, it returns the cached -value. Otherwise, a ground state simulation is performed for all input -combinations of the stored layout using the given simulation +R"doc(Logs and returns the operational status at the given point `sp = (d1, +..., dn)`. If the point has already been sampled, it returns the +cached value. Otherwise, a ground state simulation is performed for +all input combinations of the stored layout using the given simulation parameters. It terminates as soon as a non-operational state is found. -In the worst case, the function performs :math:`2^n` simulations, -where :math:`n` is the number of inputs of the layout. This function +In the worst case, the function performs :math:`2^i` simulations, +where :math:`i` is the number of inputs of the layout. This function is used by all operational domain computation techniques. Any investigated point is added to the stored `op_domain`, regardless @@ -6491,37 +6577,48 @@ can occur during the computation, each value is temporarily held in an atomic variable and written to the statistics object only after the computation has finished.)doc"; -static const char *__doc_fiction_detail_operational_domain_impl_moore_neighborhood = -R"doc(Returns the Moore neighborhood of the step point at `sp = (x, y)`. The -Moore neighborhood is the set of all points that are adjacent to `(x, -y)` including the diagonals. Thereby, the Moore neighborhood contains -up to 8 points as points outside of the parameter range are not -gathered. The points are returned in clockwise order starting from the -right neighbor. +static const char *__doc_fiction_detail_operational_domain_impl_moore_neighborhood_2d = +R"doc(Returns the 2D Moore neighborhood of the step point at `sp = (x, y)`. +The 2D Moore neighborhood is the set of all points that are adjacent +to `(x, y)` in the plane including the diagonals. Thereby, the 2D +Moore neighborhood contains up to 8 points as points outside of the +parameter range are not gathered. The points are returned in clockwise +order starting from the right neighbor. + +Parameter ``sp``: + Step point to get the 2D Moore neighborhood of. + +Returns: + The 2D Moore neighborhood of the step point at `sp = (x, y)`.)doc"; + +static const char *__doc_fiction_detail_operational_domain_impl_moore_neighborhood_3d = +R"doc(Returns the 3D Moore neighborhood of the step point at `sp = (x, y, +z)`. The 3D Moore neighborhood is the set of all points that are +adjacent to `(x, y, z)` in the 3D space including the diagonals. +Thereby, the 3D Moore neighborhood contains up to 26 points as points +outside of the parameter range are not gathered. The points are +returned in no particular order. Parameter ``sp``: - Step point to get the Moore neighborhood of. + Step point to get the 3D Moore neighborhood of. Returns: - The Moore neighborhood of the step point at `sp = (x, y)`.)doc"; + The 3D Moore neighborhood of the step point at `sp = (x, y, z)`.)doc"; + +static const char *__doc_fiction_detail_operational_domain_impl_num_dimensions = R"doc(The number of dimensions.)doc"; static const char *__doc_fiction_detail_operational_domain_impl_num_evaluated_parameter_combinations = R"doc(Number of evaluated parameter combinations.)doc"; static const char *__doc_fiction_detail_operational_domain_impl_num_simulator_invocations = R"doc(Number of simulator invocations.)doc"; -static const char *__doc_fiction_detail_operational_domain_impl_num_x_steps = -R"doc(Calculates the number of steps in the x dimension based on the +static const char *__doc_fiction_detail_operational_domain_impl_num_steps = +R"doc(Calculates the number of steps in the given dimension based on the provided parameters. Returns: - The number of steps in the x dimension.)doc"; + The number of steps in the given dimension.)doc"; -static const char *__doc_fiction_detail_operational_domain_impl_num_y_steps = -R"doc(Calculates the number of steps in the y dimension based on the -provided parameters. - -Returns: - The number of steps in the y dimension.)doc"; +static const char *__doc_fiction_detail_operational_domain_impl_num_threads = R"doc(Number of available hardware threads.)doc"; static const char *__doc_fiction_detail_operational_domain_impl_op_domain = R"doc(The operational domain of the layout.)doc"; @@ -6557,7 +6654,7 @@ Parameter ``ps``: Parameter ``st``: Statistics of the process.)doc"; -static const char *__doc_fiction_detail_operational_domain_impl_output_bdl_pairs = R"doc(The output BDL pair of the layout.)doc"; +static const char *__doc_fiction_detail_operational_domain_impl_output_bdl_pairs = R"doc(The output BDL pairs of the layout.)doc"; static const char *__doc_fiction_detail_operational_domain_impl_params = R"doc(The parameters for the operational domain computation.)doc"; @@ -6586,36 +6683,26 @@ Parameter ``val``: Parameter ``dim``: Sweep dimension to set the value `val` to.)doc"; -static const char *__doc_fiction_detail_operational_domain_impl_set_x_dimension_value = -R"doc(Helper function that sets the value of the x dimension in the -simulation parameters. +static const char *__doc_fiction_detail_operational_domain_impl_simulate_operational_status_in_parallel = +R"doc(Simulates the operational status of the given points in parallel. It +divides the work among multiple threads to speed up the computation. -Parameter ``sim_params``: - Simulation parameter object to set the x dimension value of. +@note The distribution of the work among threads is a simple slice- +based approach. If your step points are ordered, consider shuffling +the vector first for better load balancing. Otherwise, some threads +might finish early if they got assigned a slice with mainly non- +operational samples, which are faster to compute due to the early +termination condition. -Parameter ``val``: - Value to set the x dimension to.)doc"; - -static const char *__doc_fiction_detail_operational_domain_impl_set_y_dimension_value = -R"doc(Helper function that sets the value of the y dimension in the -simulation parameters. - -Parameter ``sim_params``: - Simulation parameter object to set the y dimension value of. - -Parameter ``val``: - Value to set the y dimension to.)doc"; +Parameter ``step_points``: + A vector of step points for which the operational status is to be + simulated.)doc"; static const char *__doc_fiction_detail_operational_domain_impl_stats = R"doc(The statistics of the operational domain computation.)doc"; -static const char *__doc_fiction_detail_operational_domain_impl_step_point = -R"doc(A step point represents a point in the x and y dimension from 0 to the -maximum number of steps. A step point does not hold the actual -parameter values, but the step values in the x and y dimension, -respectively. +static const char *__doc_fiction_detail_operational_domain_impl_step_point = R"doc(Forward-declare step_point.)doc"; -See `operational_domain::parameter_point` for a point that holds the -actual parameter values.)doc"; +static const char *__doc_fiction_detail_operational_domain_impl_step_point_2 = R"doc(Forward-declare step_point.)doc"; static const char *__doc_fiction_detail_operational_domain_impl_step_point_operator_eq = R"doc(Equality operator. @@ -6649,21 +6736,10 @@ static const char *__doc_fiction_detail_operational_domain_impl_step_point_step_ static const char *__doc_fiction_detail_operational_domain_impl_step_point_step_point_2 = R"doc(Standard constructor. -Parameter ``x_step``: - X dimension step value. - -Parameter ``y_step``: - Y dimension step value.)doc"; - -static const char *__doc_fiction_detail_operational_domain_impl_step_point_x = R"doc(X dimension step value.)doc"; - -static const char *__doc_fiction_detail_operational_domain_impl_step_point_y = R"doc(Y dimension step value.)doc"; - -static const char *__doc_fiction_detail_operational_domain_impl_sweep_dimension = R"doc(Potential sweep dimensions.)doc"; +Parameter ``steps``: + All dimension step values.)doc"; -static const char *__doc_fiction_detail_operational_domain_impl_sweep_dimension_X = R"doc(Sweep dimension X.)doc"; - -static const char *__doc_fiction_detail_operational_domain_impl_sweep_dimension_Y = R"doc(Sweep dimension Y.)doc"; +static const char *__doc_fiction_detail_operational_domain_impl_step_point_step_values = R"doc(All dimension step values.)doc"; static const char *__doc_fiction_detail_operational_domain_impl_to_parameter_point = R"doc(Converts a step point to a parameter point. @@ -6685,13 +6761,7 @@ Parameter ``pp``: static const char *__doc_fiction_detail_operational_domain_impl_truth_table = R"doc(The logical specification of the layout.)doc"; -static const char *__doc_fiction_detail_operational_domain_impl_x_indices = R"doc(X dimension steps.)doc"; - -static const char *__doc_fiction_detail_operational_domain_impl_x_values = R"doc(All x dimension values.)doc"; - -static const char *__doc_fiction_detail_operational_domain_impl_y_indices = R"doc(Y dimension steps.)doc"; - -static const char *__doc_fiction_detail_operational_domain_impl_y_values = R"doc(All y dimension values.)doc"; +static const char *__doc_fiction_detail_operational_domain_impl_values = R"doc(All dimension values.)doc"; static const char *__doc_fiction_detail_optimize_output_positions = R"doc(Utility function that moves outputs from the last row to the previous @@ -7429,6 +7499,18 @@ Parameter ``to_delete``: Reference to the to-delete list to be updated with new coordinates.)doc"; +static const char *__doc_fiction_detail_validate_sweep_parameters = +R"doc(This function validates the given sweep parameters for the operational +domain computation. It checks if the minimum value of any sweep +dimension is larger than the corresponding maximum value. +Additionally, it checks if the step size of any sweep dimension is +negative or zero. + +If any of this is the case, an `std::invalid_argument` is thrown. + +Parameter ``params``: + The operational domain parameters to validate.)doc"; + static const char *__doc_fiction_detail_wire_east = R"doc()doc"; static const char *__doc_fiction_detail_wire_south = R"doc()doc"; @@ -8096,7 +8178,8 @@ Parameter ``params``: Returns: Physically valid parameters with the corresponding excited state - number of the given cds for each parameter point.)doc"; + number of the given charge distribution surface for each parameter + point.)doc"; static const char *__doc_fiction_determine_propability_of_fabricating_operational_gate = R"doc(During fabrication, SiDBs may not align precisely with their intended @@ -9236,42 +9319,6 @@ Parameter ``s_last``: first 2-element sub-sequence shared between the two ranges, or `last` if no such shared sub-sequence exists.)doc"; -static const char *__doc_fiction_find_key_with_tolerance = -R"doc(This function searches for a floating-point value specified by the -`key` in the provided map `map`, applying a tolerance specified by -`fiction::physical_constants::POP_STABILITY_ERR`. Each key in the map -is compared to the specified key within this tolerance. - -Template parameter ``MapType``: - The type of the map containing parameter points as keys. - -Parameter ``map``: - The map containing parameter points as keys and associated values. - -Parameter ``key``: - The parameter point to search for in the map. - -Returns: - An iterator to the found parameter point in the map, or - `map.cend()` if not found.)doc"; - -static const char *__doc_fiction_find_parameter_point_with_tolerance = -R"doc(This function searches for a parameter point, specified by the `key`, -in the provided map `map` with tolerance. - -Template parameter ``MapType``: - The type of the map containing parameter points as keys. - -Parameter ``map``: - The map containing parameter points as keys and associated values. - -Parameter ``key``: - The parameter point to search for in the map. - -Returns: - An iterator to the found parameter point in the map, or - `map.cend()` if not found.)doc"; - static const char *__doc_fiction_flat_top_hex = R"doc(\verbatim _____ / \ / \ \ / \_____/ \endverbatim)doc"; static const char *__doc_fiction_foreach_edge = @@ -13227,32 +13274,36 @@ defined as the layout implementing the given truth table. The input BDL pairs of the layout are assumed to be in the same order as the inputs of the truth table. -This algorithm first uses random sampling to find a single operational +This algorithm first uses random sampling to find a set of operational point within the parameter range. From there, it traverses outwards to find the edge of the operational area and performs Moore neighborhood -contour tracing to explore the contour of the operational domain. If -the operational domain is connected, the algorithm is guaranteed to -find the contours of the entire operational domain within the -parameter range if the initial random sampling found an operational -point. - -It performs up to `samples` uniformly-distributed random samples -within the parameter range until an operational point is found. From -there, it performs another number of samples equal to the distance to -an edge of the operational area. Finally, it performs up to 8 samples -for each contour point (however, the actual number is usually much -lower). For each sample, the algorithm performs one operational check -on the layout, where each operational check consists of up to -:math:`2^n` exact ground state simulations, where :math:`n` is the -number of inputs of the layout. Each exact ground state simulation has -exponential complexity in of itself. Therefore, the algorithm is only -feasible for small layouts with few inputs. +contour tracing to explore the contour of the operational domain. This +is repeated for all initially sampled points that do not lie within a +contour. The algorithm is guaranteed to determine the contours of all +operational "islands" if the initial random sampling found at least +one operational point within them. Thereby, this algorithm works for +disconnected operational domains. + +It performs `samples` uniformly-distributed random samples within the +parameter range. For each thusly discovered operational island, it +performs another number of samples equal to the distance to an edge of +each operational area. Finally, it performs up to 8 samples for each +contour point (however, the actual number is usually lower). For each +sample, the algorithm performs one operational check on the layout, +where each operational check consists of up to :math:`2^n` exact +ground state simulations, where :math:`n` is the number of inputs of +the layout. Each exact ground state simulation has exponential +complexity in of itself. Therefore, the algorithm is only feasible for +small layouts with few inputs. This flavor of operational domain computation was proposed in \"Reducing the Complexity of Operational Domain Computation in Silicon Dangling Bond Logic\" by M. Walter, J. Drewniok, S. S. H. Ng, K. Walus, and R. Wille in NANOARCH 2023. +This function may throw an `std::invalid_argument` exception if the +given sweep parameters are invalid. + Template parameter ``Lyt``: SiDB cell-level layout type. @@ -13278,6 +13329,10 @@ Parameter ``stats``: Returns: The (partial) operational domain of the layout.)doc"; +static const char *__doc_fiction_operational_domain_dimensions = +R"doc(The dimensions to sweep over, ordered by priority. The first dimension +is the x dimension, the second dimension is the y dimension, etc.)doc"; + static const char *__doc_fiction_operational_domain_flood_fill = R"doc(Computes the operational domain of the given SiDB cell-level layout. The operational domain is the set of all parameter combinations for @@ -13289,10 +13344,10 @@ inputs of the truth table. This algorithm first uses random sampling to find several operational points within the parameter range. From there, it employs the "flood fill" algorithm to explore the operational domain. The algorithm is -guaranteed to find all operational areas in their entirety if the -initial random sampling found at least one operational point within -them. Thereby, this algorithm works for disconnected operational -domains. +guaranteed to determine all operational "islands" in their entirety if +the initial random sampling found at least one operational point +within them. Thereby, this algorithm works for disconnected +operational domains. It performs `samples` uniformly-distributed random samples within the parameter range. From there, it performs another number of samples @@ -13310,6 +13365,9 @@ This flavor of operational domain computation was proposed in Dangling Bond Logic\" by M. Walter, J. Drewniok, S. S. H. Ng, K. Walus, and R. Wille in NANOARCH 2023. +This function may throw an `std::invalid_argument` exception if the +given sweep parameters are invalid. + Template parameter ``Lyt``: SiDB cell-level layout type. @@ -13338,8 +13396,8 @@ Parameter ``stats``: static const char *__doc_fiction_operational_domain_get_value = R"doc(This function retrieves the value associated with the provided parameter point from the operational domain. If the parameter point is -found in the domain, its corresponding value is returned. Otherwise, a -runtime error is thrown. +found in the domain, its corresponding value is returned. Otherwise, +`std::out_of_range` is thrown. Parameter ``pp``: The parameter point to look up. @@ -13367,6 +13425,9 @@ Each exact ground state simulation has exponential complexity in of itself. Therefore, the algorithm is only feasible for small layouts with few inputs. +This function may throw an `std::invalid_argument` exception if the +given sweep parameters are invalid. + Template parameter ``Lyt``: SiDB cell-level layout type. @@ -13413,21 +13474,10 @@ parameters will be kept constant across sweeps, but the sweep parameters are adjusted in each simulation step and thus overwritten in this object.)doc"; -static const char *__doc_fiction_operational_domain_params_x_dimension = R"doc(The sweep parameter for the x dimension.)doc"; - -static const char *__doc_fiction_operational_domain_params_x_max = R"doc(The maximum value of the x dimension sweep.)doc"; - -static const char *__doc_fiction_operational_domain_params_x_min = R"doc(The minimum value of the x dimension sweep.)doc"; - -static const char *__doc_fiction_operational_domain_params_x_step = R"doc(The step size of the x dimension sweep.)doc"; - -static const char *__doc_fiction_operational_domain_params_y_dimension = R"doc(The sweep parameter for the y dimension.)doc"; - -static const char *__doc_fiction_operational_domain_params_y_max = R"doc(The maximum value of the y dimension sweep.)doc"; - -static const char *__doc_fiction_operational_domain_params_y_min = R"doc(The minimum value of the y dimension sweep.)doc"; - -static const char *__doc_fiction_operational_domain_params_y_step = R"doc(The step size of the y dimension sweep.)doc"; +static const char *__doc_fiction_operational_domain_params_sweep_dimensions = +R"doc(The dimensions to sweep over together with their value ranges, ordered +by priority. The first dimension is the x dimension, the second +dimension is the y dimension, etc.)doc"; static const char *__doc_fiction_operational_domain_random_sampling = R"doc(Computes the operational domain of the given SiDB cell-level layout. @@ -13447,6 +13497,9 @@ inputs of the layout. Each exact ground state simulation has exponential complexity in of itself. Therefore, the algorithm is only feasible for small layouts with few inputs. +This function may throw an `std::invalid_argument` exception if the +given sweep parameters are invalid. + Template parameter ``Lyt``: SiDB cell-level layout type. @@ -13488,9 +13541,17 @@ static const char *__doc_fiction_operational_domain_stats_num_operational_parame static const char *__doc_fiction_operational_domain_stats_num_simulator_invocations = R"doc(Number of simulator invocations.)doc"; -static const char *__doc_fiction_operational_domain_x_dimension = R"doc(X dimension sweep parameter.)doc"; +static const char *__doc_fiction_operational_domain_value_range = +R"doc(A range of values for a dimension sweep. The range is defined by a +minimum value, a maximum value and a step size.)doc"; + +static const char *__doc_fiction_operational_domain_value_range_dimension = R"doc(The sweep parameter of the dimension.)doc"; -static const char *__doc_fiction_operational_domain_y_dimension = R"doc(Y dimension sweep parameter.)doc"; +static const char *__doc_fiction_operational_domain_value_range_max = R"doc(The maximum value of the dimension sweep.)doc"; + +static const char *__doc_fiction_operational_domain_value_range_min = R"doc(The minimum value of the dimension sweep.)doc"; + +static const char *__doc_fiction_operational_domain_value_range_step = R"doc(The step size of the dimension sweep.)doc"; static const char *__doc_fiction_operational_input_patterns = R"doc(This function determines the input combinations for which the SiDB- @@ -13510,7 +13571,7 @@ Parameter ``spec``: Vector of truth table specifications. Parameter ``params``: - Parameters to simualte if a input combination is operational. + Parameters to simulate if a input combination is operational. Returns: The count of operational input combinations.)doc"; @@ -13706,30 +13767,23 @@ Parameter ``other``: `true` iff the parameter points are equal.)doc"; static const char *__doc_fiction_parameter_point_operator_ne = -R"doc(Inequality operator. Checks if this parameter point is unequal to -another point within a specified tolerance. The tolerance is defined -by `physical_constants::POP_STABILITY_ERR`. +R"doc(Inequality operator. Parameter ``other``: Other parameter point to compare with. Returns: - `true` iff the parameter points are not equal.)doc"; + `true` if the parameter points are not equal.)doc"; -static const char *__doc_fiction_parameter_point_parameter_point = R"doc(Standard default constructor.)doc"; +static const char *__doc_fiction_parameter_point_parameter_point = R"doc(Default constructor.)doc"; static const char *__doc_fiction_parameter_point_parameter_point_2 = R"doc(Standard constructor. -Parameter ``x_val``: - X dimension parameter value. - -Parameter ``y_val``: - Y dimension parameter value.)doc"; +Parameter ``values``: + Parameter values for each dimension.)doc"; -static const char *__doc_fiction_parameter_point_x = R"doc(X dimension parameter value.)doc"; - -static const char *__doc_fiction_parameter_point_y = R"doc(Y dimension parameter value.)doc"; +static const char *__doc_fiction_parameter_point_parameters = R"doc(Parameter values for each dimension.)doc"; static const char *__doc_fiction_path_collection = R"doc(An ordered collection of multiple paths in a layout. @@ -17091,9 +17145,11 @@ output stream. The data are written as rows, each corresponding to one set of simulation parameters and their corresponding operational status. -The output CSV format is as follows: X_DIMENSION, Y_DIMENSION, -OPERATIONAL STATUS ... subsequent rows for each set of simulation -parameters. +The output CSV format is e.g. as follows: \verbatim embed:rst .. code- +block:: RST + +epsilon_r, lambda_tf, operational status 0.0, 0.0, 0 0.1, 0.0, 1 ... +subsequent rows for each set of simulation parameters \endverbatim The operational status is a binary value represented by specified tags in `params` indicating whether the simulation parameters are within @@ -17120,9 +17176,11 @@ R"doc(Writes a CSV representation of an operational domain to the specified file. The data are written as rows, each corresponding to one set of simulation parameters and their corresponding operational status. -The output CSV format is as follows: X_DIMENSION, Y_DIMENSION, -OPERATIONAL STATUS ... subsequent rows for each set of simulation -parameters. +The output CSV format is e.g. as follows: \verbatim embed:rst .. code- +block:: RST + +epsilon_r, lambda_tf, operational status 0.0, 0.0, 0 0.1, 0.0, 1 ... +subsequent rows for each set of simulation parameters \endverbatim The operational status is a binary value represented by specified tags in `params` indicating whether the simulation parameters are within @@ -17152,6 +17210,23 @@ set.)doc"; static const char *__doc_fiction_write_operational_domain_params_operational_tag = R"doc(The tag used to represent the operational value of a parameter set.)doc"; +static const char *__doc_fiction_write_operational_domain_params_sample_writing_mode = R"doc(Mode selector for writing samples to file.)doc"; + +static const char *__doc_fiction_write_operational_domain_params_sample_writing_mode_ALL_SAMPLES = +R"doc(Write all samples, including non-operational ones. This may lead to +large file sizes.)doc"; + +static const char *__doc_fiction_write_operational_domain_params_sample_writing_mode_OPERATIONAL_ONLY = +R"doc(Write operational samples only. This can drastically reduce file size +and help with visibility in 3D plots.)doc"; + +static const char *__doc_fiction_write_operational_domain_params_writing_mode = +R"doc(Whether to write non-operational samples to the CSV file. If set to +`OPERATIONAL_ONLY`, operational samples are written exclusively. This +yields a significantly smaller CSV file. It is recommended to set this +option for 3D plots because the non-operational samples would shadow +the operational samples anyway.)doc"; + static const char *__doc_fiction_write_qca_layout = R"doc(Writes a cell-level QCA layout to a qca file that is used by QCADesigner (https://waluslab.ece.ubc.ca/qcadesigner/), a physical diff --git a/bindings/pyfiction/pyfiction.cpp b/bindings/pyfiction/pyfiction.cpp index 832eb19d7..594fa09e9 100644 --- a/bindings/pyfiction/pyfiction.cpp +++ b/bindings/pyfiction/pyfiction.cpp @@ -7,6 +7,7 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" +#include "pyfiction/algorithms/iter/bdl_input_iterator.hpp" #include "pyfiction/algorithms/network_transformation/fanout_substitution.hpp" #include "pyfiction/algorithms/network_transformation/network_balancing.hpp" #include "pyfiction/algorithms/network_transformation/technology_mapping.hpp" @@ -104,6 +105,11 @@ PYBIND11_MODULE(pyfiction, m) pyfiction::cube_coordinate(m); pyfiction::siqad_coordinate(m); pyfiction::coordinate_utility(m); + /** + * Logic + */ + pyfiction::logic_networks(m); + pyfiction::truth_tables(m); /** * Layouts */ @@ -144,10 +150,9 @@ PYBIND11_MODULE(pyfiction, m) pyfiction::determine_physically_valid_parameters(m); pyfiction::determine_displacement_robustness_domain(m); /** - * Logic + * Algorithms: Iterators */ - pyfiction::logic_networks(m); - pyfiction::truth_tables(m); + pyfiction::bdl_input_iterator(m); /** * Algorithms: Network Transformation */ diff --git a/bindings/pyfiction/test/algorithms/iter/__init__.py b/bindings/pyfiction/test/algorithms/iter/__init__.py new file mode 100644 index 000000000..5fde0b8ad --- /dev/null +++ b/bindings/pyfiction/test/algorithms/iter/__init__.py @@ -0,0 +1,16 @@ +from os.path import dirname, basename, isfile, join +import glob +import os +import sys +from pathlib import Path + +if sys.platform == "win32" and sys.version_info > (3, 8, 0) and "Z3_ROOT" in os.environ: + lib_path = Path(os.environ["Z3_ROOT"]) / "lib" + if lib_path.exists(): + os.add_dll_directory(str(lib_path)) + bin_path = Path(os.environ["Z3_ROOT"]) / "bin" + if bin_path.exists(): + os.add_dll_directory(str(bin_path)) + +modules = glob.glob(join(dirname(__file__), "*.py")) +__all__ = [basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] diff --git a/bindings/pyfiction/test/algorithms/iter/bdl_input_iterator.py b/bindings/pyfiction/test/algorithms/iter/bdl_input_iterator.py new file mode 100644 index 000000000..f0a667b22 --- /dev/null +++ b/bindings/pyfiction/test/algorithms/iter/bdl_input_iterator.py @@ -0,0 +1,182 @@ +from mnt.pyfiction import * +import unittest + + +class TestBDLInputIterator(unittest.TestCase): + def test_empty_layout(self): + layout = sidb_100_lattice((0, 0)) + + bii = bdl_input_iterator_100(layout) + + self.assertEqual(bii.num_input_pairs(), 0) + self.assertEqual(bii, 0) + self.assertNotEqual(bii, 1) + self.assertLess(bii, 1) + self.assertLessEqual(bii, 1) + self.assertGreaterEqual(bii, 0) + + def test_iteration_empty_layout(self): + layout = sidb_100_lattice((0, 0)) + + bii = bdl_input_iterator_100(layout) + + self.assertEqual(bii.num_input_pairs(), 0) + self.assertEqual(bii, 0) + self.assertEqual(bii.get_layout().num_cells(), 0) + + bii = bii + 1 + + self.assertEqual(bii.num_input_pairs(), 0) + self.assertEqual(bii, 1) + self.assertEqual(bii.get_layout().num_cells(), 0) + + bii = bii - 1 + + self.assertEqual(bii.num_input_pairs(), 0) + self.assertEqual(bii, 0) + self.assertEqual(bii.get_layout().num_cells(), 0) + + def test_manual_bdl_wire_iteration(self): + layout = sidb_100_lattice((20, 0)) + + layout.assign_cell_type((0, 0, 0), sidb_technology.cell_type.INPUT) + layout.assign_cell_type((2, 0, 0), sidb_technology.cell_type.INPUT) + + layout.assign_cell_type((6, 0, 0), sidb_technology.cell_type.NORMAL) + layout.assign_cell_type((8, 0, 0), sidb_technology.cell_type.NORMAL) + + layout.assign_cell_type((12, 0, 0), sidb_technology.cell_type.NORMAL) + layout.assign_cell_type((14, 0, 0), sidb_technology.cell_type.NORMAL) + + layout.assign_cell_type((18, 0, 0), sidb_technology.cell_type.OUTPUT) + layout.assign_cell_type((20, 0, 0), sidb_technology.cell_type.OUTPUT) + + bii = bdl_input_iterator_100(layout) + + self.assertEqual(bii.get_layout().num_cells(), + 7) # 2 inputs (1 already deleted for input pattern 0), 4 normal, 2 outputs + self.assertEqual(bii.num_input_pairs(), 1) + self.assertEqual(bii, 0) + + lyt0 = bii.get_layout() + + self.assertEqual(lyt0.get_cell_type((0, 0, 0)), sidb_technology.cell_type.INPUT) + self.assertEqual(lyt0.get_cell_type((2, 0, 0)), sidb_technology.cell_type.EMPTY) + + bii = bii + 1 + + lyt1 = bii.get_layout() + + self.assertEqual(lyt1.get_cell_type((0, 0, 0)), sidb_technology.cell_type.EMPTY) + self.assertEqual(lyt1.get_cell_type((2, 0, 0)), sidb_technology.cell_type.INPUT) + + bii = bii + 1 + + lyt2 = bii.get_layout() + + self.assertEqual(lyt2.get_cell_type((0, 0, 0)), sidb_technology.cell_type.INPUT) + self.assertEqual(lyt2.get_cell_type((2, 0, 0)), sidb_technology.cell_type.EMPTY) + + bii = bii - 1 + + lyt1 = bii.get_layout() + + self.assertEqual(lyt1.get_cell_type((0, 0, 0)), sidb_technology.cell_type.EMPTY) + self.assertEqual(lyt1.get_cell_type((2, 0, 0)), sidb_technology.cell_type.INPUT) + + bii = bii - 1 + + lyt0 = bii.get_layout() + + self.assertEqual(lyt0.get_cell_type((0, 0, 0)), sidb_technology.cell_type.INPUT) + self.assertEqual(lyt0.get_cell_type((2, 0, 0)), sidb_technology.cell_type.EMPTY) + + def test_automatic_bdl_wire_iteration(self): + layout = sidb_100_lattice((20, 0)) + + layout.assign_cell_type((0, 0, 0), sidb_technology.cell_type.INPUT) + layout.assign_cell_type((2, 0, 0), sidb_technology.cell_type.INPUT) + + layout.assign_cell_type((6, 0, 0), sidb_technology.cell_type.NORMAL) + layout.assign_cell_type((8, 0, 0), sidb_technology.cell_type.NORMAL) + + layout.assign_cell_type((12, 0, 0), sidb_technology.cell_type.NORMAL) + layout.assign_cell_type((14, 0, 0), sidb_technology.cell_type.NORMAL) + + layout.assign_cell_type((18, 0, 0), sidb_technology.cell_type.OUTPUT) + layout.assign_cell_type((20, 0, 0), sidb_technology.cell_type.OUTPUT) + + bii = bdl_input_iterator_100(layout) + + for (index, bii_iterator) in enumerate(bii): + lyt = bii_iterator.get_layout() + if index == 0: + self.assertEqual(lyt.get_cell_type((0, 0, 0)), sidb_technology.cell_type.INPUT) + self.assertEqual(lyt.get_cell_type((2, 0, 0)), sidb_technology.cell_type.EMPTY) + elif index == 1: + self.assertEqual(lyt.get_cell_type((0, 0, 0)), sidb_technology.cell_type.EMPTY) + self.assertEqual(lyt.get_cell_type((2, 0, 0)), sidb_technology.cell_type.INPUT) + elif index == 2: + self.assertEqual(lyt.get_cell_type((0, 0, 0)), sidb_technology.cell_type.INPUT) + self.assertEqual(lyt.get_cell_type((2, 0, 0)), sidb_technology.cell_type.EMPTY) + elif index == 3: + break + + def test_automatic_siqad_and_gate_iteration(self): + layout = sidb_100_lattice((20, 10), "AND gate") + + layout.assign_cell_type((0, 0, 1), sidb_technology.cell_type.INPUT) + layout.assign_cell_type((2, 1, 1), sidb_technology.cell_type.INPUT) + + layout.assign_cell_type((20, 0, 1), sidb_technology.cell_type.INPUT) + layout.assign_cell_type((18, 1, 1), sidb_technology.cell_type.INPUT) + + layout.assign_cell_type((4, 2, 1), sidb_technology.cell_type.NORMAL) + layout.assign_cell_type((6, 3, 1), sidb_technology.cell_type.NORMAL) + + layout.assign_cell_type((14, 3, 1), sidb_technology.cell_type.NORMAL) + layout.assign_cell_type((16, 2, 1), sidb_technology.cell_type.NORMAL) + + layout.assign_cell_type((10, 6, 0), sidb_technology.cell_type.OUTPUT) + layout.assign_cell_type((10, 7, 0), sidb_technology.cell_type.OUTPUT) + + layout.assign_cell_type((10, 9, 1), sidb_technology.cell_type.NORMAL) + + bii = bdl_input_iterator_100(layout) + + for (index, bii_iterator) in enumerate(bii): + lyt = bii_iterator.get_layout() + if index == 0: + self.assertEqual(lyt.get_cell_type((0, 0, 1)), sidb_technology.cell_type.INPUT) + self.assertEqual(lyt.get_cell_type((2, 1, 1)), sidb_technology.cell_type.EMPTY) + + self.assertEqual(lyt.get_cell_type((20, 0, 1)), sidb_technology.cell_type.INPUT) + self.assertEqual(lyt.get_cell_type((18, 1, 1)), sidb_technology.cell_type.EMPTY) + + elif index == 1: + self.assertEqual(lyt.get_cell_type((0, 0, 1)), sidb_technology.cell_type.INPUT) + self.assertEqual(lyt.get_cell_type((2, 1, 1)), sidb_technology.cell_type.EMPTY) + + self.assertEqual(lyt.get_cell_type((20, 0, 1)), sidb_technology.cell_type.EMPTY) + self.assertEqual(lyt.get_cell_type((18, 1, 1)), sidb_technology.cell_type.INPUT) + + elif index == 2: + self.assertEqual(lyt.get_cell_type((0, 0, 1)), sidb_technology.cell_type.EMPTY) + self.assertEqual(lyt.get_cell_type((2, 1, 1)), sidb_technology.cell_type.INPUT) + + self.assertEqual(lyt.get_cell_type((20, 0, 1)), sidb_technology.cell_type.INPUT) + self.assertEqual(lyt.get_cell_type((18, 1, 1)), sidb_technology.cell_type.EMPTY) + + elif index == 3: + self.assertEqual(lyt.get_cell_type((0, 0, 1)), sidb_technology.cell_type.EMPTY) + self.assertEqual(lyt.get_cell_type((2, 1, 1)), sidb_technology.cell_type.INPUT) + + self.assertEqual(lyt.get_cell_type((20, 0, 1)), sidb_technology.cell_type.EMPTY) + self.assertEqual(lyt.get_cell_type((18, 1, 1)), sidb_technology.cell_type.INPUT) + + else: + break + + +if __name__ == '__main__': + unittest.main() diff --git a/bindings/pyfiction/test/algorithms/simulation/sidb/test_determine_physically_valid_parameters.py b/bindings/pyfiction/test/algorithms/simulation/sidb/test_determine_physically_valid_parameters.py index dff892a16..327bb86a6 100644 --- a/bindings/pyfiction/test/algorithms/simulation/sidb/test_determine_physically_valid_parameters.py +++ b/bindings/pyfiction/test/algorithms/simulation/sidb/test_determine_physically_valid_parameters.py @@ -12,16 +12,16 @@ def test_one_DB_100_lattice(self): valid_parameters = determine_physically_valid_parameters(cds) - self.assertEqual(valid_parameters.get_excited_state_number_for_parameter(parameter_point(5, 5)), + self.assertEqual(valid_parameters.get_excited_state_number_for_parameter(parameter_point([5, 5])), 0) self.assertEqual( - valid_parameters.get_excited_state_number_for_parameter(parameter_point(5.1, 5.1)), + valid_parameters.get_excited_state_number_for_parameter(parameter_point([5.1, 5.1])), 0) # Testing for an invalid parameter point that raises an exception with self.assertRaises(ValueError): - valid_parameters.get_excited_state_number_for_parameter(parameter_point(15, 15)) + valid_parameters.get_excited_state_number_for_parameter(parameter_point([15, 15])) def test_one_DB_111_lattice(self): @@ -31,16 +31,16 @@ def test_one_DB_111_lattice(self): valid_parameters = determine_physically_valid_parameters(cds) - self.assertEqual(valid_parameters.get_excited_state_number_for_parameter(parameter_point(5, 5)), + self.assertEqual(valid_parameters.get_excited_state_number_for_parameter(parameter_point([5, 5])), 0) self.assertEqual( - valid_parameters.get_excited_state_number_for_parameter(parameter_point(5.1, 5.1)), + valid_parameters.get_excited_state_number_for_parameter(parameter_point([5.1, 5.1])), 0) # Testing for an invalid parameter point that raises an exception with self.assertRaises(ValueError): - valid_parameters.get_excited_state_number_for_parameter(parameter_point(15, 15)) + valid_parameters.get_excited_state_number_for_parameter(parameter_point([15, 15])) if __name__ == '__main__': diff --git a/bindings/pyfiction/test/algorithms/simulation/sidb/test_operational_domain.py b/bindings/pyfiction/test/algorithms/simulation/sidb/test_operational_domain.py index d9a35d9e8..acc34fb21 100644 --- a/bindings/pyfiction/test/algorithms/simulation/sidb/test_operational_domain.py +++ b/bindings/pyfiction/test/algorithms/simulation/sidb/test_operational_domain.py @@ -13,14 +13,9 @@ def test_xor_gate_100_lattice(self): params = operational_domain_params() params.sim_engine = sidb_simulation_engine.QUICKEXACT params.simulation_parameters.base = 2 - params.x_dimension = sweep_parameter.EPSILON_R - params.y_dimension = sweep_parameter.LAMBDA_TF - params.x_min = 5.55 - params.x_max = 5.65 - params.y_min = 4.95 - params.y_max = 5.05 - params.x_step = 0.01 - params.y_step = 0.01 + + params.sweep_dimensions = [operational_domain_value_range(sweep_parameter.EPSILON_R, 5.55, 5.65, 0.01), + operational_domain_value_range(sweep_parameter.LAMBDA_TF, 4.95, 5.05, 0.01)] stats_grid = operational_domain_stats() opdomain = operational_domain_grid_search(lyt, [create_xor_tt()], params, stats_grid) @@ -45,14 +40,9 @@ def test_and_gate_111_lattice(self): params.sim_engine = sidb_simulation_engine.QUICKEXACT params.simulation_parameters.base = 2 - params.x_dimension = sweep_parameter.EPSILON_R - params.y_dimension = sweep_parameter.LAMBDA_TF - params.x_min = 5.60 - params.x_max = 5.64 - params.x_step = 0.01 - params.y_min = 5.00 - params.y_max = 5.01 - params.y_step = 0.01 + + params.sweep_dimensions = [operational_domain_value_range(sweep_parameter.EPSILON_R, 5.60, 5.64, 0.01), + operational_domain_value_range(sweep_parameter.LAMBDA_TF, 5.00, 5.01, 0.01)]; stats_grid = operational_domain_stats() opdomain = operational_domain_grid_search(lyt, [create_and_tt()], params, stats_grid) diff --git a/bindings/pyfiction/test/algorithms/simulation/sidb/test_time_to_solution.py b/bindings/pyfiction/test/algorithms/simulation/sidb/test_time_to_solution.py index 9a9b79981..89157cac2 100644 --- a/bindings/pyfiction/test/algorithms/simulation/sidb/test_time_to_solution.py +++ b/bindings/pyfiction/test/algorithms/simulation/sidb/test_time_to_solution.py @@ -23,7 +23,7 @@ def test_one_sidb_100_lattice(self): self.assertGreater(stats.time_to_solution, 0.0) self.assertGreater(stats.mean_single_runtime, 0.0) - def test_one_DBs_111_lattice(self): + def test_one_sidb_111_lattice(self): layout = sidb_111_lattice((0, 0)) layout.assign_cell_type((0, 0), sidb_technology.cell_type.NORMAL) diff --git a/cli/cmd/io/tt.hpp b/cli/cmd/io/tt.hpp index 3ac3af028..6eeaadaf5 100644 --- a/cli/cmd/io/tt.hpp +++ b/cli/cmd/io/tt.hpp @@ -41,7 +41,7 @@ class tt_command : public command "table must fit the largest variable in the expression, e.g., if c is the largest " "variable, then the truth table have at least three variables.") { - add_option("table", table, "Truth table (prefix with 0x to parse as hexadecimal)"); + add_option("--table,-t", table, "Truth table (prefix with 0x to parse as hexadecimal)"); add_option("--expression,-e", expression, "Creates truth table from expression"); add_option("--random,-r", random_vars, "Creates a random truth table over variables"); } @@ -54,19 +54,19 @@ class tt_command : public command { if (is_set("table") && is_set("expression")) { - env->out() << "[w] 'table' and 'expression' cannot be set at the same time" << std::endl; + env->out() << "[e] 'table' and 'expression' cannot be set at the same time" << std::endl; reset_flags(); return; } if (is_set("table") && is_set("random")) { - env->out() << "[w] 'table' and 'random' cannot be set at the same time" << std::endl; + env->out() << "[e] 'table' and 'random' cannot be set at the same time" << std::endl; reset_flags(); return; } if (is_set("expression") && is_set("random")) { - env->out() << "[w] 'expression' and 'random' cannot be set at the same time" << std::endl; + env->out() << "[e] 'expression' and 'random' cannot be set at the same time" << std::endl; reset_flags(); return; } @@ -140,7 +140,7 @@ class tt_command : public command } else { - env->out() << "[w] no truth table generation approach specified" << std::endl; + env->out() << "[e] no truth table generation approach specified" << std::endl; } reset_flags(); diff --git a/cli/cmd/simulation/opdom.hpp b/cli/cmd/simulation/opdom.hpp index 72e099099..58fac97f8 100644 --- a/cli/cmd/simulation/opdom.hpp +++ b/cli/cmd/simulation/opdom.hpp @@ -6,6 +6,7 @@ #define FICTION_CMD_OPDOM_HPP #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +31,26 @@ namespace alice { /** + * Executes operational domain computation for the current SiDB cell-level layout in store. The operational domain is a + * set of simulation parameter values for which a given SiDB layout is logically operational. This means that a layout + * is deemed operational if the layout's ground state corresponds with a given Boolean function at the layout's outputs + * for all possible input combinations. * + * The computation comes in different flavors: + * - Grid search: Evaluates all possible parameter combinations within the specified ranges. + * - Random sampling: Evaluates a specified number of random parameter combinations. + * - Flood fill: Evaluates a specified number of random parameter combinations and then performs a flood fill to find + * the operational domain. + * - Contour tracing: Evaluates a specified number of random parameter combinations and then performs contour tracing + * to find the edges of the operational domain. + * + * The operational domain is written to a CSV file, which can be used for further analysis or visualization. + * + * For more information, see algorithms/simulation/sidb/operational_domain.hpp. + * + * BUG: Currently, the manual parameter reset (which is necessary due to an alice issue) triggers a different alice + * problem: data is not read from the CLI properly after the manual reset. This causes error messages to be displayed + * only once even if the same misconfigured command is executed twice in a row. */ class opdom_command : public command { @@ -52,25 +73,34 @@ class opdom_command : public command add_option("--flood_fill,-f", num_random_samples, "Use flood fill instead of grid search with this many initial random samples"); add_option("--contour_tracing,-c", num_random_samples, - "Use contour tracing instead of grid search with up to this many random " - "samples"); + "Use contour tracing instead of grid search with this many random samples"); add_option("filename", filename, "CSV filename to write the operational domain to")->required(); + add_flag( + "--omit_non_op_samples,-o", omit_non_operational_samples, + "Omit non-operational samples in the CSV file to reduce file size and increase visibility in 3D plots"); add_option("--epsilon_r,-e", simulation_params.epsilon_r, "Electric permittivity of the substrate (unit-less)", true); add_option("--lambda_tf,-l", simulation_params.lambda_tf, "Thomas-Fermi screening distance (unit: nm)", true); add_option("--mu_minus,-m", simulation_params.mu_minus, "Energy transition level (0/-) (unit: eV)", true); - add_option("--x_sweep", x_sweep, "Sweep parameter of the x dimension [epsilon_r, lambda_tf, mu_minus]", true); - add_option("--y_sweep", y_sweep, "Sweep parameter of the y dimension [epsilon_r, lambda_tf, mu_minus]", true); - - add_option("--x_min", params.x_min, "Minimum value of the x dimension sweep", true); - add_option("--x_max", params.x_max, "Maximum value of the x dimension sweep", true); - add_option("--x_step", params.x_step, "Step size of the x dimension sweep", true); - add_option("--y_min", params.y_min, "Minimum value of the y dimension sweep", true); - add_option("--y_max", params.y_max, "Maximum value of the y dimension sweep", true); - add_option("--y_step", params.y_step, "Step size of the y dimension sweep", true); + add_option("--x_sweep,-x", x_sweep, "Sweep parameter of the x dimension [epsilon_r, lambda_tf, mu_minus]", + true); + add_option("--y_sweep,-y", y_sweep, "Sweep parameter of the y dimension [epsilon_r, lambda_tf, mu_minus]", + true); + add_option("--z_sweep,-z", z_sweep, + "Sweep parameter of the z dimension (optional) [epsilon_r, lambda_tf, mu_minus]"); + + add_option("--x_min", sweep_dimensions[0].min, "Minimum value of the x dimension sweep", true); + add_option("--x_max", sweep_dimensions[0].max, "Maximum value of the x dimension sweep", true); + add_option("--x_step", sweep_dimensions[0].step, "Step size of the x dimension sweep", true); + add_option("--y_min", sweep_dimensions[1].min, "Minimum value of the y dimension sweep", true); + add_option("--y_max", sweep_dimensions[1].max, "Maximum value of the y dimension sweep", true); + add_option("--y_step", sweep_dimensions[1].step, "Step size of the y dimension sweep", true); + add_option("--z_min", sweep_dimensions[2].min, "Minimum value of the z dimension sweep"); + add_option("--z_max", sweep_dimensions[2].max, "Maximum value of the z dimension sweep"); + add_option("--z_step", sweep_dimensions[2].step, "Step size of the z dimension sweep"); } protected: @@ -83,29 +113,35 @@ class opdom_command : public command op_domain = {}; stats = {}; - if (simulation_params.epsilon_r <= 0) + auto& cs = store(); + + // error case: empty cell layout store + if (cs.empty()) { - env->out() << "[e] epsilon_r must be positive" << std::endl; + env->out() << "[w] no cell layout in store" << std::endl; reset_params(); return; } - if (simulation_params.lambda_tf <= 0) + + auto& ts = store(); + + // error case: empty truth table store + if (ts.empty()) { - env->out() << "[e] lambda_tf must be positive" << std::endl; + env->out() << "[w] no truth table in store" << std::endl; reset_params(); return; } - // check for valid x and y parameter bounds - if (params.x_min >= params.x_max) + if (simulation_params.epsilon_r <= 0) { - env->out() << "[e] x_min must be smaller than x_max" << std::endl; + env->out() << "[e] epsilon_r must be positive" << std::endl; reset_params(); return; } - if (params.y_min >= params.y_max) + if (simulation_params.lambda_tf <= 0) { - env->out() << "[e] y_min must be smaller than y_max" << std::endl; + env->out() << "[e] lambda_tf must be positive" << std::endl; reset_params(); return; } @@ -120,55 +156,55 @@ class opdom_command : public command return; } - auto& cs = store(); - - // error case: empty cell layout store - if (cs.empty()) + // make sure that z is not set if y is not, and that y is not set if x is not + if (is_set("z_sweep") && !is_set("y_sweep")) { - env->out() << "[w] no cell layout in store" << std::endl; + env->out() << "[e] z sweep parameter cannot be set if y sweep parameter is not set" << std::endl; + reset_params(); + return; + } + if (is_set("y_sweep") && !is_set("x_sweep")) + { + env->out() << "[e] y sweep parameter cannot be set if x sweep parameter is not set" << std::endl; reset_params(); return; } - auto& ts = store(); + // overwrite the sweeps with their respective lower-case string representations + std::transform(x_sweep.begin(), x_sweep.end(), x_sweep.begin(), ::tolower); + std::transform(y_sweep.begin(), y_sweep.end(), y_sweep.begin(), ::tolower); + std::transform(z_sweep.begin(), z_sweep.end(), z_sweep.begin(), ::tolower); - // error case: empty truth table store - if (ts.empty()) + static constexpr const std::array valid_sweep_params = {"epsilon_r", "lambda_tf", "mu_minus"}; + + // check if x sweep parameter is valid + if (std::find(valid_sweep_params.cbegin(), valid_sweep_params.cend(), x_sweep) == valid_sweep_params.cend()) { - env->out() << "[w] no truth table in store" << std::endl; + env->out() << "[e] invalid x sweep parameter \"" << x_sweep + << "\". Has to be one of [epsilon_r, lambda_tf, " + "mu_minus]" + << std::endl; reset_params(); return; } - // default sweep parameters - if (x_sweep.empty() && y_sweep.empty()) + // check if y sweep parameter is valid + if (std::find(valid_sweep_params.cbegin(), valid_sweep_params.cend(), y_sweep) == valid_sweep_params.cend()) { - x_sweep = "epsilon_r"; - y_sweep = "lambda_tf"; + env->out() << "[e] invalid y sweep parameter \"" << y_sweep + << "\". Has to be one of [epsilon_r, lambda_tf, " + "mu_minus]" + << std::endl; + reset_params(); + return; } - else - { - // overwrite x and y sweep with their respective lower-case string representations - std::transform(x_sweep.begin(), x_sweep.end(), x_sweep.begin(), ::tolower); - std::transform(y_sweep.begin(), y_sweep.end(), y_sweep.begin(), ::tolower); - static constexpr const std::array valid_sweep_params = {"epsilon_r", "lambda_tf", "mu_minus"}; - - // check if x sweep parameter is valid - if (std::find(valid_sweep_params.cbegin(), valid_sweep_params.cend(), x_sweep) == valid_sweep_params.cend()) - { - env->out() << "[e] invalid x sweep parameter \"" << x_sweep - << "\". Has to be one of [epsilon_r, lambda_tf, " - "mu_minus]" - << std::endl; - reset_params(); - return; - } - - // check if y sweep parameter is valid - if (std::find(valid_sweep_params.cbegin(), valid_sweep_params.cend(), y_sweep) == valid_sweep_params.cend()) + // check if z sweep parameter is valid if set + if (is_set("z_sweep")) + { + if (std::find(valid_sweep_params.cbegin(), valid_sweep_params.cend(), z_sweep) == valid_sweep_params.cend()) { - env->out() << "[e] invalid y sweep parameter \"" << y_sweep + env->out() << "[e] invalid z sweep parameter \"" << z_sweep << "\". Has to be one of [epsilon_r, lambda_tf, " "mu_minus]" << std::endl; @@ -180,29 +216,51 @@ class opdom_command : public command // assign x sweep parameters if (x_sweep == "epsilon_r") { - params.x_dimension = fiction::sweep_parameter::EPSILON_R; + sweep_dimensions[0].dimension = fiction::sweep_parameter::EPSILON_R; } else if (x_sweep == "lambda_tf") { - params.x_dimension = fiction::sweep_parameter::LAMBDA_TF; + sweep_dimensions[0].dimension = fiction::sweep_parameter::LAMBDA_TF; } else if (x_sweep == "mu_minus") { - params.x_dimension = fiction::sweep_parameter::MU_MINUS; + sweep_dimensions[0].dimension = fiction::sweep_parameter::MU_MINUS; } // assign y sweep parameters if (y_sweep == "epsilon_r") { - params.y_dimension = fiction::sweep_parameter::EPSILON_R; + sweep_dimensions[1].dimension = fiction::sweep_parameter::EPSILON_R; } else if (y_sweep == "lambda_tf") { - params.y_dimension = fiction::sweep_parameter::LAMBDA_TF; + sweep_dimensions[1].dimension = fiction::sweep_parameter::LAMBDA_TF; } else if (y_sweep == "mu_minus") { - params.y_dimension = fiction::sweep_parameter::MU_MINUS; + sweep_dimensions[1].dimension = fiction::sweep_parameter::MU_MINUS; + } + + if (is_set("z_sweep")) + { + // assign z sweep parameters + if (z_sweep == "epsilon_r") + { + sweep_dimensions[2].dimension = fiction::sweep_parameter::EPSILON_R; + } + else if (z_sweep == "lambda_tf") + { + sweep_dimensions[2].dimension = fiction::sweep_parameter::LAMBDA_TF; + } + else if (z_sweep == "mu_minus") + { + sweep_dimensions[2].dimension = fiction::sweep_parameter::MU_MINUS; + } + } + else + { + // remove z sweep parameter if not set + sweep_dimensions.pop_back(); } const auto get_name = [](auto&& lyt_ptr) -> std::string { return fiction::get_name(*lyt_ptr); }; @@ -217,39 +275,60 @@ class opdom_command : public command { if (lyt_ptr->num_pis() == 0 || lyt_ptr->num_pos() == 0) { - env->out() << fmt::format("[e] {} requires primary input and output cells to simulate its " + env->out() << fmt::format("[e] '{}' requires primary input and output cells to simulate its " "Boolean function", get_name(lyt_ptr)) << std::endl; + reset_params(); return; } + // set parameters params.simulation_parameters = simulation_params; + params.sim_engine = fiction::sidb_simulation_engine::QUICKEXACT; + params.sweep_dimensions = sweep_dimensions; - if (is_set("random_sampling")) - { - op_domain = fiction::operational_domain_random_sampling(*lyt_ptr, std::vector{*tt_ptr}, - num_random_samples, params, &stats); - } - else if (is_set("flood_fill")) + try { - op_domain = fiction::operational_domain_flood_fill(*lyt_ptr, std::vector{*tt_ptr}, - num_random_samples, params, &stats); + if (is_set("random_sampling")) + { + op_domain = fiction::operational_domain_random_sampling( + *lyt_ptr, std::vector{*tt_ptr}, num_random_samples, params, &stats); + } + else if (is_set("flood_fill")) + { + op_domain = fiction::operational_domain_flood_fill(*lyt_ptr, std::vector{*tt_ptr}, + num_random_samples, params, &stats); + } + else if (is_set("contour_tracing")) + { + op_domain = fiction::operational_domain_contour_tracing( + *lyt_ptr, std::vector{*tt_ptr}, num_random_samples, params, &stats); + } + else + { + op_domain = fiction::operational_domain_grid_search(*lyt_ptr, std::vector{*tt_ptr}, + params, &stats); + } } - else if (is_set("contour_tracing")) + catch (std::invalid_argument& e) { - op_domain = fiction::operational_domain_contour_tracing(*lyt_ptr, std::vector{*tt_ptr}, - num_random_samples, params, &stats); + env->out() << fmt::format("[e] {}", e.what()) << std::endl; + reset_params(); + return; } - else + catch (...) { - op_domain = fiction::operational_domain_grid_search(*lyt_ptr, std::vector{*tt_ptr}, - params, &stats); + env->out() << "[e] an unknown error occurred during operational domain computation" << std::endl; + reset_params(); + return; } } else { env->out() << fmt::format("[e] {} is not an SiDB layout", get_name(lyt_ptr)) << std::endl; + reset_params(); + return; } }; @@ -262,9 +341,16 @@ class opdom_command : public command private: /** - * Physical parameters for the simulation. + * Default physical parameters for the simulation. */ fiction::sidb_simulation_parameters simulation_params{2, -0.32, 5.6, 5.0}; + /** + * Default value ranges for sweeping. + */ + std::vector sweep_dimensions{ + {fiction::sweep_parameter::EPSILON_R, 1.0, 10.0, 0.1}, + {fiction::sweep_parameter::LAMBDA_TF, 1.0, 10.0, 0.1}, + {fiction::sweep_parameter::MU_MINUS, -0.50, -0.10, 0.025}}; /** * Operational domain parameters. */ @@ -280,15 +366,23 @@ class opdom_command : public command /** * User input for the x dimension sweep parameter. */ - std::string x_sweep{}; + std::string x_sweep{"epsilon_r"}; /** * User input for the y dimension sweep parameter. */ - std::string y_sweep{}; + std::string y_sweep{"lambda_tf"}; + /** + * User input for the z dimension sweep parameter. + */ + std::string z_sweep{}; /** * CSV filename to write the operational domain to. */ std::string filename{}; + /** + * Flag to omit non-operational samples. + */ + bool omit_non_operational_samples = false; /** * The operational domain. */ @@ -297,9 +391,24 @@ class opdom_command : public command /** * Writes the operational domain to the specified CSV file. */ - void write_op_domain() const + void write_op_domain() { - static const fiction::write_operational_domain_params write_opdom_params{"1", "0"}; + // if the operational domain call was unsuccessful, do not attempt writing anything + if (op_domain.operational_values.empty()) + { + reset_params(); + return; + } + + // set up parameters + fiction::write_operational_domain_params write_opdom_params{}; + write_opdom_params.non_operational_tag = "0"; + write_opdom_params.operational_tag = "1"; + write_opdom_params.writing_mode = + omit_non_operational_samples ? + fiction::write_operational_domain_params::sample_writing_mode::OPERATIONAL_ONLY : + fiction::write_operational_domain_params::sample_writing_mode::ALL_SAMPLES; + try { fiction::write_operational_domain(op_domain, filename, write_opdom_params); @@ -307,6 +416,14 @@ class opdom_command : public command catch (const std::exception& e) { env->out() << fmt::format("[e] {}", e.what()) << std::endl; + reset_params(); + return; + } + catch (...) + { + env->out() << "[e] an unknown error occurred while writing the operational domain data" << std::endl; + reset_params(); + return; } } /** @@ -330,10 +447,18 @@ class opdom_command : public command void reset_params() { simulation_params = fiction::sidb_simulation_parameters{2, -0.32, 5.6, 5.0}; - params = {}; - x_sweep = {}; - y_sweep = {}; - filename = {}; + sweep_dimensions = std::vector{ + {fiction::sweep_parameter::EPSILON_R, 1.0, 10.0, 0.1}, + {fiction::sweep_parameter::LAMBDA_TF, 1.0, 10.0, 0.1}, + {fiction::sweep_parameter::MU_MINUS, -0.50, -0.10, 0.025}}; + params = {}; + + x_sweep = "epsilon_r"; + y_sweep = "lambda_tf"; + z_sweep = ""; + filename = ""; + + omit_non_operational_samples = false; } }; diff --git a/docs/algorithms/iterators.rst b/docs/algorithms/iterators.rst index 7536af9a3..52422782f 100644 --- a/docs/algorithms/iterators.rst +++ b/docs/algorithms/iterators.rst @@ -19,7 +19,15 @@ Gray Code Iterator BDL Input Iterator ------------------ -**Header:** ``fiction/algorithms/iter/bdl_input_iterator.hpp`` - -.. doxygenclass:: fiction::bdl_input_iterator - :members: +.. tabs:: + .. tab:: C++ + **Header:** ``fiction/algorithms/iter/bdl_input_iterator.hpp`` + + .. doxygenclass:: fiction::bdl_input_iterator + :members: + + .. tab:: Python + .. autoclass:: mnt.pyfiction.bdl_input_iterator_100 + :members: + .. autoclass:: mnt.pyfiction.bdl_input_iterator_111 + :members: diff --git a/docs/algorithms/sidb_simulation.rst b/docs/algorithms/sidb_simulation.rst index 6b6170c1f..c11b532bb 100644 --- a/docs/algorithms/sidb_simulation.rst +++ b/docs/algorithms/sidb_simulation.rst @@ -238,8 +238,6 @@ Operational Domain Computation .. doxygenenum:: fiction::sweep_parameter .. doxygenstruct:: fiction::operational_domain :members: - .. doxygenfunction:: fiction::find_parameter_point_with_tolerance - .. doxygenfunction:: fiction::find_key_with_tolerance .. doxygenstruct:: fiction::operational_domain_params :members: .. doxygenstruct:: fiction::operational_domain_stats @@ -264,6 +262,8 @@ Operational Domain Computation :members: .. autoclass:: mnt.pyfiction.operational_domain :members: + .. autoclass:: mnt.pyfiction.operational_domain_value_range + :members: .. autoclass:: mnt.pyfiction.operational_domain_params :members: .. autoclass:: mnt.pyfiction.operational_domain_stats diff --git a/docs/cli.rst b/docs/cli.rst index f9862c765..22f1c583a 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -75,8 +75,8 @@ Truth tables ############ The ``truth_table`` store (``-t``) can hold logic descriptions in binary format as implemented in the ``kitty`` library -by Mathias Soeken. These can either be loaded via command ``tt`` by specifying a bit/hex string or a Boolean expression, -or be obtained by :ref:`simulating` a ``network`` or ``gate_layout`` via command +by Mathias Soeken. These can either be loaded via command ``tt`` by specifying a bit/hex string (``-t``) or a Boolean +expression (``-e``), or be obtained by :ref:`simulating` a ``network`` or ``gate_layout`` via command ``simulate``. Generating a ``truth_table`` from an expression uses the following syntax (from the `kitty documentation `_): @@ -87,7 +87,7 @@ if-then-else, or ``!{!a!b}`` to describe the application of De Morgan's law to ` fit the largest variable in the expression, e.g., if ``c`` is the largest variable, then the truth table has at least three variables. -Alternatively, ``tt 0110`` or ``tt 0xaffe`` generate a ``truth_table`` from bit/hex strings. +For example, ``tt -t 0110`` and ``tt -t 0xaffe`` generate a ``truth_table`` from bit and hex strings, respectively. Logic synthesis ############### @@ -364,8 +364,9 @@ processed by other tools. The parameter space to sweep over can be specified by the user via the flags - ``--x_sweep`` - ``--y_sweep`` +- ``--z_sweep`` which have to be either ``epsilon_r``, ``lambda_tf``, or ``mu_minus``. The default is ``epsilon_r`` for ``--x_sweep`` and -``lambda_tf`` for ``--y_sweep``. +``lambda_tf`` for ``--y_sweep``, with ``--z_sweep`` being an optional third sweep dimension. Additionally, min, max, and step size values can be specified for each parameter using the flags - ``--x_min`` @@ -374,7 +375,12 @@ Additionally, min, max, and step size values can be specified for each parameter - ``--y_min`` - ``--y_max`` - ``--y_step`` -respectively. The default values are 1, 10, and 0.1 on both axis, for min, max, and step, respectively. +- ``--z_min`` +- ``--z_max`` +- ``--z_step`` +respectively. The default values are 1, 10, and 0.1 on x and y axis, for min, max, and step, respectively. The z axis +is not used by default. However, if ``--z_sweep`` is specified, the default values are -0.5, -0.1, and 0.025 for min, max, +and step, respectively, assuming z to be used for ``mu_minus``. By default, grid search is applied to explore the operational domain. The algorithm can be changed by specifying one of the following options: diff --git a/docs/utils/utils.rst b/docs/utils/utils.rst index db9700010..8503831d5 100644 --- a/docs/utils/utils.rst +++ b/docs/utils/utils.rst @@ -277,6 +277,8 @@ Math Utils .. doxygenfunction:: fiction::integral_abs .. doxygenfunction:: fiction::binomial_coefficient .. doxygenfunction:: fiction::determine_all_combinations_of_distributing_k_entities_on_n_positions +.. doxygenfunction:: fiction::cartesian_combinations + ``phmap`` --------- diff --git a/experiments/operational_domain/operational_domain_3d_bestagon.cpp b/experiments/operational_domain/operational_domain_3d_bestagon.cpp new file mode 100644 index 000000000..62ce3696c --- /dev/null +++ b/experiments/operational_domain/operational_domain_3d_bestagon.cpp @@ -0,0 +1,211 @@ +// +// Created by marcel on 22.07.24. +// + +#include "fiction_experiments.hpp" // experiment class + +#include // operational domain computation algorithms +#include // SiDB simulation engines +#include // SiDB simulation parameters +#include // reader for SiDB layouts +#include // writer for operational domains +#include // pre-defined types suitable for the FCN domain +#include // truth tables helper functions + +#include // output formatting +#include // stopwatch for time measurement + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace fiction; + +int main() // NOLINT +{ + experiments::experiment + opdomain_exp{ + "Operational Domain Bestagon 3D", + "Name", + "#SiDBs", // Benchmark + "#Samples (GS)", + "op. (GS)", + "sim calls (GS)", + "t in s (GS)", // Grid Search + "#Samples (RS)", + "op. (RS)", + "sim calls (RS)", + "t in s (RS)", // Random Sampling + "#Samples (FF)", + "op. (FF)", + "sim calls (FF)", + "t in s (FF)" // Flood Fill + }; + + // simulation parameters + sidb_simulation_parameters sim_params{}; + sim_params.base = 2; + sim_params.mu_minus = -0.32; + + // operational domain parameters + operational_domain_params op_domain_params{}; + op_domain_params.simulation_parameters = sim_params; + op_domain_params.sim_engine = sidb_simulation_engine::QUICKEXACT; + op_domain_params.sweep_dimensions = {{sweep_parameter::EPSILON_R}, + {sweep_parameter::LAMBDA_TF}, + {sweep_parameter::MU_MINUS}}; + op_domain_params.sweep_dimensions[0].min = 1.0; + op_domain_params.sweep_dimensions[0].max = 10.0; + op_domain_params.sweep_dimensions[0].step = 0.05; + op_domain_params.sweep_dimensions[1].min = 1.0; + op_domain_params.sweep_dimensions[1].max = 10.0; + op_domain_params.sweep_dimensions[1].step = 0.05; + op_domain_params.sweep_dimensions[2].min = -0.50; + op_domain_params.sweep_dimensions[2].max = -0.10; + op_domain_params.sweep_dimensions[2].step = 0.0025; + + // write operational domain parameters + write_operational_domain_params write_op_domain_params{}; + write_op_domain_params.non_operational_tag = "0"; + write_op_domain_params.operational_tag = "1"; + write_op_domain_params.writing_mode = write_operational_domain_params::sample_writing_mode::OPERATIONAL_ONLY; + + static const std::string folder = fmt::format("{}bestagon_gates_type_tags/", EXPERIMENTS_PATH); + + static const std::array>, 10> gates = { + std::make_pair("and", std::vector{create_and_tt()}), + std::make_pair("nand", std::vector{create_nand_tt()}), + std::make_pair("nor", std::vector{create_nor_tt()}), + std::make_pair("xnor", std::vector{create_xnor_tt()}), + std::make_pair("xor", std::vector{create_xor_tt()}), + std::make_pair("or", std::vector{create_or_tt()}), + std::make_pair("wire", std::vector{create_id_tt()}), + std::make_pair("wire_diag", std::vector{create_id_tt()}), + std::make_pair("inv", std::vector{create_not_tt()}), + std::make_pair("inv_diag", std::vector{create_not_tt()})}; + + // total number of samples + static std::size_t total_samples_gs = 0; + static std::size_t total_samples_rs = 0; + static std::size_t total_samples_ff = 0; + + // total number of simulator calls + static std::size_t total_sim_calls_gs = 0; + static std::size_t total_sim_calls_rs = 0; + static std::size_t total_sim_calls_ff = 0; + + // total runtime + static double total_runtime_gs = 0.0; + static double total_runtime_rs = 0.0; + static double total_runtime_ff = 0.0; + + for (const auto& [gate, truth_table] : gates) + { + for (const auto& file : std::filesystem::directory_iterator(fmt::format("{}{}", folder, gate))) + { + const auto& benchmark = file.path(); + + std::cout << benchmark << std::endl; + + auto lyt = read_sqd_layout(benchmark.string(), gate); + + // operational domain stats + operational_domain_stats op_domain_stats_gs{}; + operational_domain_stats op_domain_stats_rs{}; + operational_domain_stats op_domain_stats_ff{}; + + // compute the operational domains + const auto op_domain_gs = + operational_domain_grid_search(lyt, truth_table, op_domain_params, &op_domain_stats_gs); + const auto op_domain_rs = + operational_domain_random_sampling(lyt, truth_table, 20000, op_domain_params, &op_domain_stats_rs); + const auto op_domain_ff = + operational_domain_flood_fill(lyt, truth_table, 2000, op_domain_params, &op_domain_stats_ff); + + // write the operational domains to a CSV file + write_operational_domain(op_domain_gs, + fmt::format("{}operational_domain_grid_search_3d_bestagon_{}.csv", folder, gate), + write_op_domain_params); + write_operational_domain( + op_domain_rs, fmt::format("{}operational_domain_random_sampling_3d_bestagon_{}.csv", folder, gate), + write_op_domain_params); + write_operational_domain(op_domain_ff, + fmt::format("{}operational_domain_flood_fill_3d_bestagon_{}.csv", folder, gate), + write_op_domain_params); + + // update the total number of samples + total_samples_gs += op_domain_stats_gs.num_evaluated_parameter_combinations; + total_samples_rs += op_domain_stats_rs.num_evaluated_parameter_combinations; + total_samples_ff += op_domain_stats_ff.num_evaluated_parameter_combinations; + + // update the total number of simulator calls + total_sim_calls_gs += op_domain_stats_gs.num_simulator_invocations; + total_sim_calls_rs += op_domain_stats_rs.num_simulator_invocations; + total_sim_calls_ff += op_domain_stats_ff.num_simulator_invocations; + + // update the total runtime + total_runtime_gs += mockturtle::to_seconds(op_domain_stats_gs.time_total); + total_runtime_rs += mockturtle::to_seconds(op_domain_stats_rs.time_total); + total_runtime_ff += mockturtle::to_seconds(op_domain_stats_ff.time_total); + + // compute the operational percentages + const auto operational_percentage_gs = + static_cast(op_domain_stats_gs.num_operational_parameter_combinations) / + static_cast(op_domain_stats_gs.num_evaluated_parameter_combinations); + const auto operational_percentage_rs = + static_cast(op_domain_stats_rs.num_operational_parameter_combinations) / + static_cast(op_domain_stats_rs.num_evaluated_parameter_combinations); + const auto operational_percentage_ff = + static_cast(op_domain_stats_ff.num_operational_parameter_combinations) / + static_cast(op_domain_stats_ff.num_evaluated_parameter_combinations); + + opdomain_exp( + // Benchmark + benchmark.string(), lyt.num_cells(), + + // Grid Search + op_domain_stats_gs.num_evaluated_parameter_combinations, operational_percentage_gs, + op_domain_stats_gs.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_gs.time_total), + + // Random Sampling + op_domain_stats_rs.num_evaluated_parameter_combinations, operational_percentage_rs, + op_domain_stats_rs.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_rs.time_total), + + // Flood Fill + op_domain_stats_ff.num_evaluated_parameter_combinations, operational_percentage_ff, + op_domain_stats_ff.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_ff.time_total) + + ); + } + + opdomain_exp.save(); + opdomain_exp.table(); + } + + // log the total number of samples and simulator calls + opdomain_exp( + // Benchmark + "Total", 0, + + // Grid Search + total_samples_gs, 0.0, total_sim_calls_gs, total_runtime_gs, + + // Random Sampling + total_samples_rs, 0.0, total_sim_calls_rs, total_runtime_rs, + + // Flood Fill + total_samples_ff, 0.0, total_sim_calls_ff, total_runtime_ff + + ); + + opdomain_exp.save(); + opdomain_exp.table(); + + return EXIT_SUCCESS; +} diff --git a/experiments/operational_domain/operational_domain_3d_siqad.cpp b/experiments/operational_domain/operational_domain_3d_siqad.cpp new file mode 100644 index 000000000..80a0db0e1 --- /dev/null +++ b/experiments/operational_domain/operational_domain_3d_siqad.cpp @@ -0,0 +1,205 @@ +// +// Created by marcel on 22.07.24. +// + +#include "fiction_experiments.hpp" // experiment class + +#include // operational domain computation algorithms +#include // SiDB simulation engines +#include // SiDB simulation parameters +#include // reader for SiDB layouts +#include // writer for operational domains +#include // pre-defined types suitable for the FCN domain +#include // truth tables helper functions + +#include // string formatting +#include // stopwatch for measuring time + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace fiction; + +int main() // NOLINT +{ + experiments::experiment + opdomain_exp{ + "Operational Domain SiQAD 3D", + "Name", + "#SiDBs", // Benchmark + "#Samples (GS)", + "op. (GS)", + "sim calls (GS)", + "t in s (GS)", // Grid Search + "#Samples (RS)", + "op. (RS)", + "sim calls (RS)", + "t in s (RS)", // Random Sampling + "#Samples (FF)", + "op. (FF)", + "sim calls (FF)", + "t in s (FF)" // Flood Fill + }; + + // simulation parameters + sidb_simulation_parameters sim_params{}; + sim_params.base = 2; + sim_params.mu_minus = -0.28; + + // operational domain parameters + operational_domain_params op_domain_params{}; + op_domain_params.simulation_parameters = sim_params; + op_domain_params.sim_engine = sidb_simulation_engine::QUICKEXACT; + op_domain_params.sweep_dimensions = {{sweep_parameter::EPSILON_R}, + {sweep_parameter::LAMBDA_TF}, + {sweep_parameter::MU_MINUS}}; + op_domain_params.sweep_dimensions[0].min = 1.0; + op_domain_params.sweep_dimensions[0].max = 10.0; + op_domain_params.sweep_dimensions[0].step = 0.05; + op_domain_params.sweep_dimensions[1].min = 1.0; + op_domain_params.sweep_dimensions[1].max = 10.0; + op_domain_params.sweep_dimensions[1].step = 0.05; + op_domain_params.sweep_dimensions[2].min = -0.50; + op_domain_params.sweep_dimensions[2].max = -0.10; + op_domain_params.sweep_dimensions[2].step = 0.0025; + + // write operational domain parameters + write_operational_domain_params write_op_domain_params{}; + write_op_domain_params.non_operational_tag = "0"; + write_op_domain_params.operational_tag = "1"; + write_op_domain_params.writing_mode = write_operational_domain_params::sample_writing_mode::OPERATIONAL_ONLY; + + static const std::string folder = fmt::format("{}siqad_gates_type_tags/", EXPERIMENTS_PATH); + + static const std::array>, 5> gates = { + std::make_pair("and", std::vector{create_and_tt()}), + std::make_pair("nand", std::vector{create_nand_tt()}), + std::make_pair("xnor", std::vector{create_xnor_tt()}), + std::make_pair("xor", std::vector{create_xor_tt()}), std::make_pair("or", std::vector{create_or_tt()})}; + + // total number of samples + static std::size_t total_samples_gs = 0; + static std::size_t total_samples_rs = 0; + static std::size_t total_samples_ff = 0; + + // total number of simulator calls + static std::size_t total_sim_calls_gs = 0; + static std::size_t total_sim_calls_rs = 0; + static std::size_t total_sim_calls_ff = 0; + + // total runtime + static double total_runtime_gs = 0.0; + static double total_runtime_rs = 0.0; + static double total_runtime_ff = 0.0; + + for (const auto& [gate, truth_table] : gates) + { + for (const auto& file : std::filesystem::directory_iterator(fmt::format("{}{}", folder, gate))) + { + const auto& benchmark = file.path(); + + std::cout << benchmark << std::endl; + + const auto lyt = read_sqd_layout(benchmark.string(), gate); + + // operational domain stats + operational_domain_stats op_domain_stats_gs{}; + operational_domain_stats op_domain_stats_rs{}; + operational_domain_stats op_domain_stats_ff{}; + + // compute the operational domains + const auto op_domain_gs = + operational_domain_grid_search(lyt, truth_table, op_domain_params, &op_domain_stats_gs); + const auto op_domain_rs = + operational_domain_random_sampling(lyt, truth_table, 20000, op_domain_params, &op_domain_stats_rs); + const auto op_domain_ff = + operational_domain_flood_fill(lyt, truth_table, 2000, op_domain_params, &op_domain_stats_ff); + + // write the operational domains to a CSV file + write_operational_domain(op_domain_gs, + fmt::format("{}operational_domain_grid_search_3d_siqad_{}.csv", folder, gate), + write_op_domain_params); + write_operational_domain(op_domain_rs, + fmt::format("{}operational_domain_random_sampling_3d_siqad_{}.csv", folder, gate), + write_op_domain_params); + write_operational_domain(op_domain_ff, + fmt::format("{}operational_domain_flood_fill_3d_siqad_{}.csv", folder, gate), + write_op_domain_params); + + // update the total number of samples + total_samples_gs += op_domain_stats_gs.num_evaluated_parameter_combinations; + total_samples_rs += op_domain_stats_rs.num_evaluated_parameter_combinations; + total_samples_ff += op_domain_stats_ff.num_evaluated_parameter_combinations; + + // update the total number of simulator calls + total_sim_calls_gs += op_domain_stats_gs.num_simulator_invocations; + total_sim_calls_rs += op_domain_stats_rs.num_simulator_invocations; + total_sim_calls_ff += op_domain_stats_ff.num_simulator_invocations; + + // update the total runtime + total_runtime_gs += mockturtle::to_seconds(op_domain_stats_gs.time_total); + total_runtime_rs += mockturtle::to_seconds(op_domain_stats_rs.time_total); + total_runtime_ff += mockturtle::to_seconds(op_domain_stats_ff.time_total); + + // compute the operational percentages + const auto operational_percentage_gs = + static_cast(op_domain_stats_gs.num_operational_parameter_combinations) / + static_cast(op_domain_stats_gs.num_evaluated_parameter_combinations); + const auto operational_percentage_rs = + static_cast(op_domain_stats_rs.num_operational_parameter_combinations) / + static_cast(op_domain_stats_rs.num_evaluated_parameter_combinations); + const auto operational_percentage_ff = + static_cast(op_domain_stats_ff.num_operational_parameter_combinations) / + static_cast(op_domain_stats_ff.num_evaluated_parameter_combinations); + + opdomain_exp( + // Benchmark + benchmark.string(), lyt.num_cells(), + + // Grid Search + op_domain_stats_gs.num_evaluated_parameter_combinations, operational_percentage_gs, + op_domain_stats_gs.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_gs.time_total), + + // Random Sampling + op_domain_stats_rs.num_evaluated_parameter_combinations, operational_percentage_rs, + op_domain_stats_rs.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_rs.time_total), + + // Flood Fill + op_domain_stats_ff.num_evaluated_parameter_combinations, operational_percentage_ff, + op_domain_stats_ff.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_ff.time_total) + + ); + } + + opdomain_exp.save(); + opdomain_exp.table(); + } + + // log the total number of samples and simulator calls + opdomain_exp( + // Benchmark + "Total", 0, + + // Grid Search + total_samples_gs, 0.0, total_sim_calls_gs, total_runtime_gs, + + // Random Sampling + total_samples_rs, 0.0, total_sim_calls_rs, total_runtime_rs, + + // Flood Fill + total_samples_ff, 0.0, total_sim_calls_ff, total_runtime_ff + + ); + + opdomain_exp.save(); + opdomain_exp.table(); + + return EXIT_SUCCESS; +} diff --git a/experiments/operational_domain/operational_domain_bestagon.cpp b/experiments/operational_domain/operational_domain_bestagon.cpp new file mode 100644 index 000000000..767b2489b --- /dev/null +++ b/experiments/operational_domain/operational_domain_bestagon.cpp @@ -0,0 +1,232 @@ +// +// Created by marcel on 08.08.23. +// + +#include "fiction_experiments.hpp" // experiment class + +#include // operational domain computation algorithms +#include // SiDB simulation engines +#include // SiDB simulation parameters +#include // reader for SiDB layouts +#include // writer for operational domains +#include // pre-defined types suitable for the FCN domain +#include // truth tables helper functions + +#include // output formatting +#include // stopwatch for time measurement + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace fiction; + +int main() // NOLINT +{ + experiments::experiment + opdomain_exp{ + "Operational Domain Bestagon", + "Name", + "#SiDBs", // Benchmark + "#Samples (GS)", + "op. (GS)", + "sim calls (GS)", + "t in s (GS)", // Grid Search + "#Samples (RS)", + "op. (RS)", + "sim calls (RS)", + "t in s (RS)", // Random Sampling + "#Samples (FF)", + "op. (FF)", + "sim calls (FF)", + "t in s (FF)", // Flood Fill + "#Samples (CT)", + "op. (CT)", + "sim calls (CT)", + "t in s (CT)" // Contour Tracing + }; + + // simulation parameters + sidb_simulation_parameters sim_params{}; + sim_params.base = 2; + sim_params.mu_minus = -0.32; + + // operational domain parameters + operational_domain_params op_domain_params{}; + op_domain_params.simulation_parameters = sim_params; + op_domain_params.sim_engine = sidb_simulation_engine::QUICKEXACT; + op_domain_params.sweep_dimensions = {{sweep_parameter::EPSILON_R}, {sweep_parameter::LAMBDA_TF}}; + op_domain_params.sweep_dimensions[0].min = 1.0; + op_domain_params.sweep_dimensions[0].max = 10.0; + op_domain_params.sweep_dimensions[0].step = 0.05; + op_domain_params.sweep_dimensions[1].min = 1.0; + op_domain_params.sweep_dimensions[1].max = 10.0; + op_domain_params.sweep_dimensions[1].step = 0.05; + + // write operational domain parameters + write_operational_domain_params write_op_domain_params{}; + write_op_domain_params.non_operational_tag = "0"; + write_op_domain_params.operational_tag = "1"; + write_op_domain_params.writing_mode = write_operational_domain_params::sample_writing_mode::ALL_SAMPLES; + + static const std::string folder = fmt::format("{}bestagon_gates_type_tags/", EXPERIMENTS_PATH); + + static const std::array>, 10> gates = { + std::make_pair("and", std::vector{create_and_tt()}), + std::make_pair("nand", std::vector{create_nand_tt()}), + std::make_pair("nor", std::vector{create_nor_tt()}), + std::make_pair("xnor", std::vector{create_xnor_tt()}), + std::make_pair("xor", std::vector{create_xor_tt()}), + std::make_pair("or", std::vector{create_or_tt()}), + std::make_pair("wire", std::vector{create_id_tt()}), + std::make_pair("wire_diag", std::vector{create_id_tt()}), + std::make_pair("inv", std::vector{create_not_tt()}), + std::make_pair("inv_diag", std::vector{create_not_tt()})}; + + // total number of samples + static std::size_t total_samples_gs = 0; + static std::size_t total_samples_rs = 0; + static std::size_t total_samples_ff = 0; + static std::size_t total_samples_ct = 0; + + // total number of simulator calls + static std::size_t total_sim_calls_gs = 0; + static std::size_t total_sim_calls_rs = 0; + static std::size_t total_sim_calls_ff = 0; + static std::size_t total_sim_calls_ct = 0; + + // total runtime + static double total_runtime_gs = 0.0; + static double total_runtime_rs = 0.0; + static double total_runtime_ff = 0.0; + static double total_runtime_ct = 0.0; + + for (const auto& [gate, truth_table] : gates) + { + for (const auto& file : std::filesystem::directory_iterator(fmt::format("{}{}", folder, gate))) + { + const auto& benchmark = file.path(); + + std::cout << benchmark << std::endl; + + auto lyt = read_sqd_layout(benchmark.string(), gate); + + // operational domain stats + operational_domain_stats op_domain_stats_gs{}; + operational_domain_stats op_domain_stats_rs{}; + operational_domain_stats op_domain_stats_ff{}; + operational_domain_stats op_domain_stats_ct{}; + + // compute the operational domains + const auto op_domain_gs = + operational_domain_grid_search(lyt, truth_table, op_domain_params, &op_domain_stats_gs); + const auto op_domain_rs = + operational_domain_random_sampling(lyt, truth_table, 2500, op_domain_params, &op_domain_stats_rs); + const auto op_domain_ff = + operational_domain_flood_fill(lyt, truth_table, 250, op_domain_params, &op_domain_stats_ff); + const auto op_domain_ct = + operational_domain_contour_tracing(lyt, truth_table, 100, op_domain_params, &op_domain_stats_ct); + + // write the operational domains to a CSV file + write_operational_domain(op_domain_gs, + fmt::format("{}operational_domain_grid_search_bestagon_{}.csv", folder, gate), + write_op_domain_params); + write_operational_domain(op_domain_rs, + fmt::format("{}operational_domain_random_sampling_bestagon_{}.csv", folder, gate), + write_op_domain_params); + write_operational_domain(op_domain_ff, + fmt::format("{}operational_domain_flood_fill_bestagon_{}.csv", folder, gate), + write_op_domain_params); + write_operational_domain(op_domain_ct, + fmt::format("{}operational_domain_contour_tracing_bestagon_{}.csv", folder, gate), + write_op_domain_params); + + // update the total number of samples + total_samples_gs += op_domain_stats_gs.num_evaluated_parameter_combinations; + total_samples_rs += op_domain_stats_rs.num_evaluated_parameter_combinations; + total_samples_ff += op_domain_stats_ff.num_evaluated_parameter_combinations; + total_samples_ct += op_domain_stats_ct.num_evaluated_parameter_combinations; + + // update the total number of simulator calls + total_sim_calls_gs += op_domain_stats_gs.num_simulator_invocations; + total_sim_calls_rs += op_domain_stats_rs.num_simulator_invocations; + total_sim_calls_ff += op_domain_stats_ff.num_simulator_invocations; + total_sim_calls_ct += op_domain_stats_ct.num_simulator_invocations; + + // update the total runtime + total_runtime_gs += mockturtle::to_seconds(op_domain_stats_gs.time_total); + total_runtime_rs += mockturtle::to_seconds(op_domain_stats_rs.time_total); + total_runtime_ff += mockturtle::to_seconds(op_domain_stats_ff.time_total); + total_runtime_ct += mockturtle::to_seconds(op_domain_stats_ct.time_total); + + // compute the operational percentages + const auto operational_percentage_gs = + static_cast(op_domain_stats_gs.num_operational_parameter_combinations) / + static_cast(op_domain_stats_gs.num_evaluated_parameter_combinations); + const auto operational_percentage_rs = + static_cast(op_domain_stats_rs.num_operational_parameter_combinations) / + static_cast(op_domain_stats_rs.num_evaluated_parameter_combinations); + const auto operational_percentage_ff = + static_cast(op_domain_stats_ff.num_operational_parameter_combinations) / + static_cast(op_domain_stats_ff.num_evaluated_parameter_combinations); + const auto operational_percentage_ct = + static_cast(op_domain_stats_ct.num_operational_parameter_combinations) / + static_cast(op_domain_stats_ct.num_evaluated_parameter_combinations); + + opdomain_exp( + // Benchmark + benchmark.string(), lyt.num_cells(), + + // Grid Search + op_domain_stats_gs.num_evaluated_parameter_combinations, operational_percentage_gs, + op_domain_stats_gs.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_gs.time_total), + + // Random Sampling + op_domain_stats_rs.num_evaluated_parameter_combinations, operational_percentage_rs, + op_domain_stats_rs.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_rs.time_total), + + // Flood Fill + op_domain_stats_ff.num_evaluated_parameter_combinations, operational_percentage_ff, + op_domain_stats_ff.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_ff.time_total), + + // Contour Tracing + op_domain_stats_ct.num_evaluated_parameter_combinations, operational_percentage_ct, + op_domain_stats_ct.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_ct.time_total) + + ); + } + + opdomain_exp.save(); + opdomain_exp.table(); + } + + // log the total number of samples and simulator calls + opdomain_exp( + // Benchmark + "Total", 0, + + // Grid Search + total_samples_gs, 0.0, total_sim_calls_gs, total_runtime_gs, + + // Random Sampling + total_samples_rs, 0.0, total_sim_calls_rs, total_runtime_rs, + + // Flood Fill + total_samples_ff, 0.0, total_sim_calls_ff, total_runtime_ff, + + // Contour Tracing + total_samples_ct, 0.0, total_sim_calls_ct, total_runtime_ct + + ); + + opdomain_exp.save(); + opdomain_exp.table(); + + return EXIT_SUCCESS; +} diff --git a/experiments/operational_domain/operational_domain_siqad.cpp b/experiments/operational_domain/operational_domain_siqad.cpp index e2a32daa8..ab7f50488 100644 --- a/experiments/operational_domain/operational_domain_siqad.cpp +++ b/experiments/operational_domain/operational_domain_siqad.cpp @@ -4,18 +4,16 @@ #include "fiction_experiments.hpp" // experiment class -#include // operational domain computation algorithms -#include -#include -#include // reader for SiDB layouts -#include // writer for operational domains -#include -#include +#include // operational domain computation algorithms +#include // SiDB simulation engines +#include // SiDB simulation parameters +#include // reader for SiDB layouts +#include // writer for operational domains #include // pre-defined types suitable for the FCN domain #include // truth tables helper functions -#include // string formatting -#include // truth tables +#include // string formatting +#include // stopwatch for measuring time #include #include @@ -33,32 +31,49 @@ int main() // NOLINT experiments::experiment opdomain_exp{ - "Operational Domain", "Name", "#SiDBs", // Benchmark - "#Samples (GS)", "op. (GS)", "sim calls (GS)", "t in s (GS)", // Grid Search - "#Samples (RS)", "op. (RS)", "sim calls (RS)", "t in s (RS)", // Random Sampling - "#Samples (FF)", "op. (FF)", "sim calls (FF)", "t in s (FF)", // Flood Fill - "#Samples (CT)", "op. (CT)", "sim calls (CT)", "t in s (CT)" // Contour Tracing + "Operational Domain SiQAD", + "Name", + "#SiDBs", // Benchmark + "#Samples (GS)", + "op. (GS)", + "sim calls (GS)", + "t in s (GS)", // Grid Search + "#Samples (RS)", + "op. (RS)", + "sim calls (RS)", + "t in s (RS)", // Random Sampling + "#Samples (FF)", + "op. (FF)", + "sim calls (FF)", + "t in s (FF)", // Flood Fill + "#Samples (CT)", + "op. (CT)", + "sim calls (CT)", + "t in s (CT)" // Contour Tracing }; // simulation parameters sidb_simulation_parameters sim_params{}; - sim_params.base = 2; + sim_params.base = 2; + sim_params.mu_minus = -0.28; // operational domain parameters operational_domain_params op_domain_params{}; - op_domain_params.simulation_parameters = sim_params; - op_domain_params.sim_engine = sidb_simulation_engine::QUICKEXACT; - op_domain_params.x_dimension = sweep_parameter::EPSILON_R; - op_domain_params.x_min = 1.0; - op_domain_params.x_max = 10.0; - op_domain_params.x_step = 0.05; - op_domain_params.y_dimension = sweep_parameter::LAMBDA_TF; - op_domain_params.y_min = 1.0; - op_domain_params.y_max = 10.0; - op_domain_params.y_step = 0.05; + op_domain_params.simulation_parameters = sim_params; + op_domain_params.sim_engine = sidb_simulation_engine::QUICKEXACT; + op_domain_params.sweep_dimensions = {{sweep_parameter::EPSILON_R}, {sweep_parameter::LAMBDA_TF}}; + op_domain_params.sweep_dimensions[0].min = 1.0; + op_domain_params.sweep_dimensions[0].max = 10.0; + op_domain_params.sweep_dimensions[0].step = 0.05; + op_domain_params.sweep_dimensions[1].min = 1.0; + op_domain_params.sweep_dimensions[1].max = 10.0; + op_domain_params.sweep_dimensions[1].step = 0.05; // write operational domain parameters - static const write_operational_domain_params write_op_domain_params{"1", "0"}; + write_operational_domain_params write_op_domain_params{}; + write_op_domain_params.non_operational_tag = "0"; + write_op_domain_params.operational_tag = "1"; + write_op_domain_params.writing_mode = write_operational_domain_params::sample_writing_mode::ALL_SAMPLES; static const std::string folder = fmt::format("{}siqad_gates_type_tags/", EXPERIMENTS_PATH); @@ -80,6 +95,12 @@ int main() // NOLINT static std::size_t total_sim_calls_ff = 0; static std::size_t total_sim_calls_ct = 0; + // total runtime + static double total_runtime_gs = 0.0; + static double total_runtime_rs = 0.0; + static double total_runtime_ff = 0.0; + static double total_runtime_ct = 0.0; + for (const auto& [gate, truth_table] : gates) { for (const auto& file : std::filesystem::directory_iterator(fmt::format("{}{}", folder, gate))) @@ -132,6 +153,12 @@ int main() // NOLINT total_sim_calls_ff += op_domain_stats_ff.num_simulator_invocations; total_sim_calls_ct += op_domain_stats_ct.num_simulator_invocations; + // update the total runtime + total_runtime_gs += mockturtle::to_seconds(op_domain_stats_gs.time_total); + total_runtime_rs += mockturtle::to_seconds(op_domain_stats_rs.time_total); + total_runtime_ff += mockturtle::to_seconds(op_domain_stats_ff.time_total); + total_runtime_ct += mockturtle::to_seconds(op_domain_stats_ct.time_total); + // compute the operational percentages const auto operational_percentage_gs = static_cast(op_domain_stats_gs.num_operational_parameter_combinations) / @@ -164,11 +191,13 @@ int main() // NOLINT // Contour Tracing op_domain_stats_ct.num_evaluated_parameter_combinations, operational_percentage_ct, - op_domain_stats_ct.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_ct.time_total)); + op_domain_stats_ct.num_simulator_invocations, mockturtle::to_seconds(op_domain_stats_ct.time_total) + + ); } opdomain_exp.save(); - opdomain_exp.table(); + // opdomain_exp.table(); } // log the total number of samples and simulator calls @@ -177,19 +206,21 @@ int main() // NOLINT "Total", 0, // Grid Search - total_samples_gs, 0.0, total_sim_calls_gs, 0.0, + total_samples_gs, 0.0, total_sim_calls_gs, total_runtime_gs, // Random Sampling - total_samples_rs, 0.0, total_sim_calls_rs, 0.0, + total_samples_rs, 0.0, total_sim_calls_rs, total_runtime_rs, // Flood Fill - total_samples_ff, 0.0, total_sim_calls_ff, 0.0, + total_samples_ff, 0.0, total_sim_calls_ff, total_runtime_ff, // Contour Tracing - total_samples_ct, 0.0, total_sim_calls_ct, 0.0); + total_samples_ct, 0.0, total_sim_calls_ct, total_runtime_ct + + ); opdomain_exp.save(); - opdomain_exp.table(); + // opdomain_exp.table(); return EXIT_SUCCESS; } diff --git a/include/fiction/algorithms/iter/bdl_input_iterator.hpp b/include/fiction/algorithms/iter/bdl_input_iterator.hpp index 0f223eafb..51e15b5d5 100644 --- a/include/fiction/algorithms/iter/bdl_input_iterator.hpp +++ b/include/fiction/algorithms/iter/bdl_input_iterator.hpp @@ -7,6 +7,7 @@ #include "fiction/algorithms/simulation/sidb/detect_bdl_pairs.hpp" #include "fiction/technology/cell_technologies.hpp" +#include "fiction/traits.hpp" #include "fiction/types.hpp" #include @@ -42,8 +43,7 @@ class bdl_input_iterator */ explicit bdl_input_iterator(const Lyt& lyt, const detect_bdl_pairs_params& params = {}) noexcept : layout{lyt.clone()}, - input_pairs{detect_bdl_pairs(layout, sidb_technology::cell_type::INPUT, params)}, - num_inputs{static_cast(input_pairs.size())} + input_pairs{detect_bdl_pairs(layout, sidb_technology::cell_type::INPUT, 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"); @@ -271,9 +271,9 @@ class bdl_input_iterator * * @return The number of input BDL pairs. */ - [[nodiscard]] uint64_t get_number_of_inputs() const noexcept + [[nodiscard]] uint64_t num_input_pairs() const noexcept { - return num_inputs; + return input_pairs.size(); } private: @@ -285,10 +285,6 @@ class bdl_input_iterator * The detected input BDL pairs. */ const std::vector>> input_pairs; - /** - * The amount of input BDL pairs. - */ - const uint8_t num_inputs; /** * The current input index. There are \f$2^n\f$ possible input states for an \f$n\f$-input BDL layout. */ @@ -302,7 +298,7 @@ class bdl_input_iterator */ void set_all_inputs() noexcept { - for (uint8_t i = 0; i < num_inputs; ++i) + for (uint64_t i = 0; i < input_pairs.size(); ++i) { const auto& input_i = input_pairs[i]; diff --git a/include/fiction/algorithms/simulation/sidb/determine_physically_valid_parameters.hpp b/include/fiction/algorithms/simulation/sidb/determine_physically_valid_parameters.hpp index 3d85a291b..173ad7000 100644 --- a/include/fiction/algorithms/simulation/sidb/determine_physically_valid_parameters.hpp +++ b/include/fiction/algorithms/simulation/sidb/determine_physically_valid_parameters.hpp @@ -30,8 +30,8 @@ namespace fiction * @tparam Lyt The charge distribution surface type. * @param cds The charge distribution surface for which physical parameters are to be determined. * @param params Operational domain parameters. - * @return Physically valid parameters with the corresponding excited state number of the given cds for each parameter - * point. + * @return Physically valid parameters with the corresponding excited state number of the given charge distribution + * surface for each parameter point. */ template [[nodiscard]] operational_domain @@ -41,9 +41,10 @@ determine_physically_valid_parameters(Lyt& cds, const operational_domain_params& static_assert(has_sidb_technology_v, "Lyt is not an SiDB layout"); static_assert(is_charge_distribution_surface_v, "Lyt is not a charge distribution surface"); - operational_domain_stats st{}; - detail::operational_domain_impl> p{cds, params, - st}; + operational_domain_stats st{}; + + using op_domain = operational_domain; + detail::operational_domain_impl p{cds, params, st}; const auto result = p.grid_search_for_physically_valid_parameters(cds); diff --git a/include/fiction/algorithms/simulation/sidb/displacement_robustness_domain.hpp b/include/fiction/algorithms/simulation/sidb/displacement_robustness_domain.hpp index a3b457298..e22b18e43 100644 --- a/include/fiction/algorithms/simulation/sidb/displacement_robustness_domain.hpp +++ b/include/fiction/algorithms/simulation/sidb/displacement_robustness_domain.hpp @@ -484,36 +484,6 @@ class displacement_robustness_domain_impl return all_possible_sidb_misplacements; } - /** - * This is a helper function, which recursively generates combinations of SiDB displacements for all SiDBs - * based on the provided vector of displacement vectors. - * - * @param result The vector to store the generated combinations. The first element describes the SiDBs of the first - * displaced layout. - * @param current_combination The current combination being constructed. - * @param cell_index The current cell_index in the vector of displacement vectors. - */ - void generate_all_possible_combinations_of_displacements(std::vector>>& result, - std::vector>& current_combination, - const std::size_t cell_index) noexcept - { - if (cell_index == all_possible_sidb_displacements.size()) - { - result.push_back(current_combination); - return; - } - - // Recursively generate combinations for each cell in the current displacement vector - for (const auto& c : all_possible_sidb_displacements[cell_index]) - { - // Add current cell to the combination - current_combination.push_back(c); - // Recursively generate next combinations - generate_all_possible_combinations_of_displacements(result, current_combination, cell_index + 1); - // Backtrack: remove current cell to explore other combinations - current_combination.pop_back(); - } - } /** * This function generates all SiDB layouts with displacements based on the original layout. * It filters out layouts where two or more SiDBs would be on the same spot due to displacement. @@ -522,9 +492,7 @@ class displacement_robustness_domain_impl */ [[nodiscard]] std::vector generate_valid_displaced_sidb_layouts() noexcept { - std::vector>> all_possible_sidb_displacement{}; - std::vector> current_combination{}; - generate_all_possible_combinations_of_displacements(all_possible_sidb_displacement, current_combination, 0); + auto all_possible_sidb_displacement = cartesian_combinations(all_possible_sidb_displacements); std::shuffle(all_possible_sidb_displacement.begin(), all_possible_sidb_displacement.end(), generator); diff --git a/include/fiction/algorithms/simulation/sidb/is_operational.hpp b/include/fiction/algorithms/simulation/sidb/is_operational.hpp index b4603e499..47afbbfae 100644 --- a/include/fiction/algorithms/simulation/sidb/is_operational.hpp +++ b/include/fiction/algorithms/simulation/sidb/is_operational.hpp @@ -32,10 +32,11 @@ namespace fiction { + /** * Possible operational status of a layout. */ -enum class operational_status +enum class operational_status : uint8_t { /** * The layout is operational. @@ -46,6 +47,7 @@ enum class operational_status */ NON_OPERATIONAL }; + /** * Parameters for the `is_operational` algorithm. */ @@ -312,6 +314,7 @@ class is_operational_impl physical_simulation_of_layout(const bdl_input_iterator& bdl_iterator) noexcept { assert(parameters.simulation_parameters.base == 2 && "base number is set to 3"); + if (parameters.sim_engine == sidb_simulation_engine::EXGS) { // perform an exhaustive ground state simulation @@ -383,7 +386,7 @@ is_operational(const Lyt& lyt, const std::vector& spec, const is_operational * @tparam TT Type of the truth table. * @param lyt The SiDB layout. * @param spec Vector of truth table specifications. - * @param params Parameters to simualte if a input combination is operational. + * @param params Parameters to simulate if a input combination is operational. * @return The count of operational input combinations. */ template diff --git a/include/fiction/algorithms/simulation/sidb/operational_domain.hpp b/include/fiction/algorithms/simulation/sidb/operational_domain.hpp index 9396d22e4..c85a90e39 100644 --- a/include/fiction/algorithms/simulation/sidb/operational_domain.hpp +++ b/include/fiction/algorithms/simulation/sidb/operational_domain.hpp @@ -15,10 +15,11 @@ #include "fiction/technology/cell_technologies.hpp" #include "fiction/technology/physical_constants.hpp" #include "fiction/traits.hpp" -#include "fiction/utils/execution_utils.hpp" #include "fiction/utils/hash.hpp" +#include "fiction/utils/math_utils.hpp" #include "fiction/utils/phmap_utils.hpp" +#include #include #include #include @@ -35,8 +36,9 @@ #include #include #include -#include +#include #include +#include #include #include #include @@ -50,24 +52,19 @@ namespace fiction struct parameter_point { /** - * Standard default constructor. + * Default constructor. */ parameter_point() = default; /** * Standard constructor. * - * @param x_val X dimension parameter value. - * @param y_val Y dimension parameter value. + * @param values Parameter values for each dimension. */ - parameter_point(const double x_val, const double y_val) : x{x_val}, y{y_val} {} + explicit parameter_point(const std::vector& values) : parameters(values) {} /** - * X dimension parameter value. + * Parameter values for each dimension. */ - double x; - /** - * Y dimension parameter value. - */ - double y; + std::vector parameters{}; /** * Equality operator. Checks if this parameter point is equal to another point within a specified tolerance. * The tolerance is defined by `physical_constants::POP_STABILITY_ERR`. @@ -77,17 +74,33 @@ struct parameter_point */ [[nodiscard]] bool operator==(const parameter_point& other) const noexcept { + // Check if sizes are equal + if (parameters.size() != other.parameters.size()) + { + return false; + } + + // Define tolerance for comparison constexpr auto tolerance = physical_constants::POP_STABILITY_ERR; - return std::abs(x - other.x) < tolerance && std::abs(y - other.y) < tolerance; + + // Compare each element with tolerance + for (std::size_t i = 0; i < parameters.size(); ++i) + { + if (std::fabs(parameters[i] - other.parameters[i]) >= tolerance) + { + return false; + } + } + + return true; } /** - * Inequality operator. Checks if this parameter point is unequal to another point within a specified tolerance. - * The tolerance is defined by `physical_constants::POP_STABILITY_ERR`. + * Inequality operator. * * @param other Other parameter point to compare with. - * @return `true` iff the parameter points are not equal. + * @return `true` if the parameter points are not equal. */ - [[nodiscard]] bool operator!=(const parameter_point& other) const noexcept + bool operator!=(const parameter_point& other) const noexcept { return !(*this == other); } @@ -98,24 +111,20 @@ struct parameter_point * @return The parameter value at the specified index. */ template - auto get() const noexcept + auto get() const { - static_assert(I < 2, "Index out of bounds for parameter_point"); - - if constexpr (I == 0) - { - return x; - } - else // I == 1 + if (I >= parameters.size()) { - return y; + throw std::out_of_range("Index out of bounds for parameter_point"); } + + return parameters[I]; } }; /** * Possible sweep parameters for the operational domain computation. */ -enum class sweep_parameter +enum class sweep_parameter : uint8_t { /** * The relative permittivity of the dielectric material. @@ -130,7 +139,15 @@ enum class sweep_parameter */ MU_MINUS }; - +namespace detail +{ +/** + * Forward-declaration for `operational_domain`. + */ +template +std::optional contains_parameter_point(const MapType& map, + const typename MapType::key_type& key); +} // namespace detail /** * An operational domain is a set of simulation parameter values for which a given SiDB layout is logically operational. * This means that a layout is deemed operational if the layout's ground state corresponds with a given Boolean function @@ -150,72 +167,69 @@ template struct operational_domain { /** - * X dimension sweep parameter. + * The dimensions to sweep over, ordered by priority. The first dimension is the x dimension, the second dimension + * is the y dimension, etc. */ - sweep_parameter x_dimension{sweep_parameter::EPSILON_R}; - /** - * Y dimension sweep parameter. - */ - sweep_parameter y_dimension{sweep_parameter::LAMBDA_TF}; + std::vector dimensions{}; /** * This can store different information depending on the use case. If the operational domain is simulated for * different physical parameters, the parameters are stored with the corresponding operating status. */ locked_parallel_flat_hash_map operational_values{}; /** - * This function retrieves the value associated with the provided parameter point - * from the operational domain. If the parameter point is found in the domain, - * its corresponding value is returned. Otherwise, a runtime error is thrown. + * This function retrieves the value associated with the provided parameter point from the operational domain. If + * the parameter point is found in the domain, its corresponding value is returned. Otherwise, `std::out_of_range` + * is thrown. * * @param pp The parameter point to look up. * @return The value associated with the parameter point. */ - [[nodiscard]] uint64_t get_value(const parameter_point& pp) const + [[nodiscard]] Value get_value(const parameter_point& pp) const { - if (const auto it = find_parameter_point_with_tolerance(operational_values, pp); - it != operational_values.cend()) + if (const auto v = detail::contains_parameter_point(operational_values, pp); v.has_value()) + { + return v.value().second; + } + + // Create a string stream to hold the string representation + std::stringstream ss; + + // Iterate over the vector and add elements to the string stream + for (std::size_t i = 0; i < pp.parameters.size(); ++i) { - return it->second; + ss << pp.parameters[i]; + + if (i != pp.parameters.size() - 1) + { + ss << ", "; + } } - throw std::out_of_range(fmt::format("({},{}) not found in the operational domain", pp.x, pp.y).c_str()); + + throw std::out_of_range(fmt::format("{} not found in the operational domain", ss.str()).c_str()); } }; /** - * This function searches for a parameter point, specified by the `key`, in the provided map - * `map` with tolerance. - * - * @tparam MapType The type of the map containing parameter points as keys. - * @param map The map containing parameter points as keys and associated values. - * @param key The parameter point to search for in the map. - * @return An iterator to the found parameter point in the map, or `map.cend()` if not found. + * A range of values for a dimension sweep. The range is defined by a minimum value, a maximum value and a step size. */ -template -[[maybe_unused]] static typename MapType::const_iterator -find_parameter_point_with_tolerance(const MapType& map, const typename MapType::key_type& key) +struct operational_domain_value_range { - static_assert(std::is_same_v, "Map key type must be parameter_point"); - - return std::find_if(map.cbegin(), map.cend(), [&key](const auto& pair) { return pair.first == key; }); -} -/** - * This function searches for a floating-point value specified by the `key` in the provided map `map`, - * applying a tolerance specified by `fiction::physical_constants::POP_STABILITY_ERR`. - * Each key in the map is compared to the specified key within this tolerance. - * - * @tparam MapType The type of the map containing parameter points as keys. - * @param map The map containing parameter points as keys and associated values. - * @param key The parameter point to search for in the map. - * @return An iterator to the found parameter point in the map, or `map.cend()` if not found. - */ -template -[[maybe_unused]] static typename MapType::const_iterator find_key_with_tolerance(const MapType& map, - const typename MapType::key_type& key) -{ - static_assert(std::is_floating_point_v, "Map key type must be floating-point"); - constexpr double tolerance = physical_constants::POP_STABILITY_ERR; - auto compare_keys = [&key, &tolerance](const auto& pair) { return std::abs(pair.first - key) < tolerance; }; - return std::find_if(map.begin(), map.end(), compare_keys); -} + /** + * The sweep parameter of the dimension. + */ + sweep_parameter dimension; + /** + * The minimum value of the dimension sweep. + */ + double min{1.0}; + /** + * The maximum value of the dimension sweep. + */ + double max{10.0}; + /** + * The step size of the dimension sweep. + */ + double step{0.1}; +}; /** * Parameters for the operational domain computation. The parameters are used across the different operational domain * computation algorithms. @@ -232,37 +246,12 @@ struct operational_domain_params */ sidb_simulation_engine sim_engine{sidb_simulation_engine::QUICKEXACT}; /** - * The sweep parameter for the x dimension. - */ - sweep_parameter x_dimension{sweep_parameter::EPSILON_R}; - /** - * The minimum value of the x dimension sweep. - */ - double x_min{1.0}; - /** - * The maximum value of the x dimension sweep. - */ - double x_max{10.0}; - /** - * The step size of the x dimension sweep. + * The dimensions to sweep over together with their value ranges, ordered by priority. The first dimension is the x + * dimension, the second dimension is the y dimension, etc. */ - double x_step{0.1}; - /** - * The sweep parameter for the y dimension. - */ - sweep_parameter y_dimension{sweep_parameter::LAMBDA_TF}; - /** - * The minimum value of the y dimension sweep. - */ - double y_min{1.0}; - /** - * The maximum value of the y dimension sweep. - */ - double y_max{10.0}; - /** - * The step size of the y dimension sweep. - */ - double y_step{0.1}; + std::vector sweep_dimensions{ + operational_domain_value_range{sweep_parameter::EPSILON_R, 1.0, 10.0, 0.1}, + operational_domain_value_range{sweep_parameter::LAMBDA_TF, 1.0, 10.0, 0.1}}; /** * The parameters for the BDL pair detection, which is necessary during the operational domain computation to * detect input and output BDL pairs. @@ -300,7 +289,78 @@ struct operational_domain_stats namespace detail { -template +/** + * This function validates the given sweep parameters for the operational domain computation. It checks if the minimum + * value of any sweep dimension is larger than the corresponding maximum value. Additionally, it checks if the step size + * of any sweep dimension is negative or zero. + * + * If any of this is the case, an `std::invalid_argument` is thrown. + * + * @param params The operational domain parameters to validate. + */ +void validate_sweep_parameters(const operational_domain_params& params) +{ + for (auto d = 0u; d < params.sweep_dimensions.size(); ++d) + { + if (params.sweep_dimensions[d].max < params.sweep_dimensions[d].min) + { + throw std::invalid_argument( + fmt::format("Invalid sweep dimension: 'max' value is smaller than 'min' value for " + "dimension {}", + d)); + } + if (params.sweep_dimensions[d].step <= 0.0) + { + throw std::invalid_argument( + fmt::format("Invalid sweep dimension: 'step' size is negative or 0 for dimension {}", d)); + } + } +} +/** + * This function checks for the containment of a parameter point, specified by `key`, in the provided map `map`. If the + * parameter point is found in the map, the associated `MapType::value_type` is returned. Otherwise, `std::nullopt` is + * returned. + * + * @tparam MapType The type of the map containing parameter points as keys. + * @param map The map in which to search for `key`. + * @param key The parameter point to search for in `map`. + * @return The associated `MapType::value_type` of `key` in `map`, or `std::nullopt` if `key` is not contained in `map`. + */ +template +std::optional contains_parameter_point(const MapType& map, + const typename MapType::key_type& key) +{ + static_assert(std::is_same_v, "Map key type must be parameter_point"); + + std::optional result = std::nullopt; + + map.if_contains(key, [&result](const typename MapType::value_type& v) { result.emplace(v); }); + + return result; +} +/** + * This function searches for a floating-point value specified by the `key` in the provided map `map`, applying a + * tolerance specified by `fiction::physical_constants::POP_STABILITY_ERR`. Each key in the map is compared to the + * specified key within this tolerance. + * + * @tparam MapType The type of the map containing parameter points as keys. + * @param map The map containing parameter points as keys and associated values. + * @param key The parameter point to search for in the map. + * @return An iterator to the found parameter point in the map, or `map.cend()` if not found. + */ +template +typename MapType::const_iterator find_key_with_tolerance(const MapType& map, const typename MapType::key_type& key) +{ + static_assert(std::is_floating_point_v, "Map key type must be floating-point"); + + constexpr double tolerance = physical_constants::POP_STABILITY_ERR; + + auto compare_keys = [&key, &tolerance](const auto& pair) { return std::abs(pair.first - key) < tolerance; }; + + return std::find_if(map.cbegin(), map.cend(), compare_keys); +} + +template class operational_domain_impl { public: @@ -320,44 +380,40 @@ class operational_domain_impl truth_table{tt}, params{ps}, stats{st}, - output_bdl_pairs{detect_bdl_pairs(lyt, sidb_technology::cell_type::OUTPUT, ps.bdl_params)}, - x_indices(num_x_steps() + 1), // pre-allocate the x dimension indices - y_indices(num_y_steps() + 1) // pre-allocate the y dimension indices + output_bdl_pairs{detect_bdl_pairs(layout, sidb_technology::cell_type::OUTPUT, params.bdl_params)}, + num_dimensions{params.sweep_dimensions.size()} { - x_values.reserve(num_x_steps() + 1); - y_values.reserve(num_y_steps() + 1); + op_domain.dimensions.reserve(num_dimensions); - op_domain.x_dimension = params.x_dimension; - op_domain.y_dimension = params.y_dimension; + indices.reserve(num_dimensions); + values.reserve(num_dimensions); - std::iota(x_indices.begin(), x_indices.end(), 0ul); - std::iota(y_indices.begin(), y_indices.end(), 0ul); - - // if the value of the x-parameter is greater than params.x_max after num_x_steps() steps, this value is - // ignored in the operational domain calculation. - if ((params.x_min + static_cast(x_indices.size() - 1) * params.x_step) - params.x_max > - physical_constants::POP_STABILITY_ERR) - { - x_indices.pop_back(); - } - // if the value of the y-parameter is greater than params.y_max after num_y_steps() steps, this value is - // ignored in the operational domain calculation. - if (((params.y_min + static_cast(y_indices.size() - 1) * params.y_step) - params.y_max) > - physical_constants::POP_STABILITY_ERR) + for (auto d = 0u; d < num_dimensions; ++d) { - y_indices.pop_back(); - } + op_domain.dimensions.push_back(params.sweep_dimensions[d].dimension); + + // generate the step points for the dimension + indices.push_back(std::vector(num_steps(d) + 1)); + std::iota(indices[d].begin(), indices[d].end(), 0ul); + + // if the value of the parameter is greater than params.max after num_x_steps() steps, this value is + // ignored in the operational domain calculation + if ((params.sweep_dimensions[d].min + + static_cast(indices[d].size() - 1) * params.sweep_dimensions[d].step) - + params.sweep_dimensions[d].max > + physical_constants::POP_STABILITY_ERR) + { + indices[d].pop_back(); + } - // generate the x dimension values - for (std::size_t i = 0; i < x_indices.size(); ++i) - { - x_values.push_back(params.x_min + static_cast(i) * params.x_step); - } + values.emplace_back(); - // generate the y dimension values - for (std::size_t i = 0; i < y_indices.size(); ++i) - { - y_values.push_back(params.y_min + static_cast(i) * params.y_step); + // generate the values for the dimension + for (const auto i : indices[d]) + { + values[d].push_back(params.sweep_dimensions[d].min + + static_cast(i) * params.sweep_dimensions[d].step); + } } } /** @@ -373,44 +429,39 @@ class operational_domain_impl truth_table{std::vector{}}, params{ps}, stats{st}, - x_indices(num_x_steps() + 1), // pre-allocate the x dimension indices - y_indices(num_y_steps() + 1) // pre-allocate the y dimension indices + num_dimensions{params.sweep_dimensions.size()} { + op_domain.dimensions.reserve(num_dimensions); - x_values.reserve(num_x_steps() + 1); - y_values.reserve(num_y_steps() + 1); - - op_domain.x_dimension = params.x_dimension; - op_domain.y_dimension = params.y_dimension; + indices.reserve(num_dimensions); + values.reserve(num_dimensions); - std::iota(x_indices.begin(), x_indices.end(), 0ul); - std::iota(y_indices.begin(), y_indices.end(), 0ul); - - // if the value of the x-parameter is greater than params.x_max after num_x_steps() steps, this value is - // ignored in the operational domain calculation. - if ((params.x_min + static_cast(x_indices.size() - 1) * params.x_step) - params.x_max > - physical_constants::POP_STABILITY_ERR) - { - x_indices.pop_back(); - } - // if the value of the y-parameter is greater than params.y_max after num_y_steps() steps, this value is - // ignored in the operational domain calculation. - if (((params.y_min + static_cast(y_indices.size() - 1) * params.y_step) - - params.y_max) > physical_constants::POP_STABILITY_ERR) + for (auto d = 0u; d < num_dimensions; ++d) { - y_indices.pop_back(); - } + op_domain.dimensions.push_back(params.sweep_dimensions[d].dimension); + + // generate the step points for the dimension + indices.push_back(std::vector(num_steps(d) + 1)); + std::iota(indices[d].begin(), indices[d].end(), 0ul); + + // if the value of the parameter is greater than params.max after num_x_steps() steps, this value is + // ignored in the operational domain calculation + if ((params.sweep_dimensions[d].min + + static_cast(indices[d].size() - 1) * params.sweep_dimensions[d].step) - + params.sweep_dimensions[d].max > + physical_constants::POP_STABILITY_ERR) + { + indices[d].pop_back(); + } - // generate the x dimension values - for (std::size_t i = 0; i < x_indices.size(); ++i) - { - x_values.push_back(params.x_min + static_cast(i) * params.x_step); - } + values.emplace_back(); - // generate the y dimension values - for (std::size_t i = 0; i < y_indices.size(); ++i) - { - y_values.push_back(params.y_min + static_cast(i) * params.y_step); + // generate the values for the dimension + for (const auto i : indices[d]) + { + values[d].push_back(params.sweep_dimensions[d].min + + static_cast(i) * params.sweep_dimensions[d].step); + } } } /** @@ -423,14 +474,22 @@ class operational_domain_impl { mockturtle::stopwatch stop{stats.time_total}; - // for each x value in parallel - std::for_each(FICTION_EXECUTION_POLICY_PAR_UNSEQ x_indices.cbegin(), x_indices.cend(), - [this](const auto x) - { - // for each y value in parallel - std::for_each(FICTION_EXECUTION_POLICY_PAR_UNSEQ y_indices.cbegin(), y_indices.cend(), - [this, x](const auto y) { is_step_point_operational({x, y}); }); - }); + const auto all_index_combinations = cartesian_combinations(indices); + + std::vector all_step_points{}; + all_step_points.reserve(all_index_combinations.size()); + + std::transform(all_index_combinations.cbegin(), all_index_combinations.cend(), + std::back_inserter(all_step_points), [](const auto& comb) noexcept { return step_point{comb}; }); + + // shuffle the step points to simulate in random order. This helps with load-balancing since + // operational/non-operational points are usually clustered. However, non-operational points can be simulated + // faster on average because of the early termination condition. Thus, threads that mainly simulate + // non-operational points will finish earlier and will be idle while other threads are still simulating the more + // expensive operational points + std::shuffle(all_step_points.begin(), all_step_points.end(), std::mt19937_64{std::random_device{}()}); + + simulate_operational_status_in_parallel(all_step_points); log_stats(); @@ -450,9 +509,7 @@ class operational_domain_impl const auto step_point_samples = generate_random_step_points(samples); - // for each sample point in parallel - std::for_each(FICTION_EXECUTION_POLICY_PAR_UNSEQ step_point_samples.cbegin(), step_point_samples.cend(), - [this](const auto& sp) { is_step_point_operational(sp); }); + simulate_operational_status_in_parallel(step_point_samples); log_stats(); @@ -470,25 +527,39 @@ class operational_domain_impl */ [[nodiscard]] operational_domain flood_fill(const std::size_t samples) noexcept { + assert((num_dimensions == 2 || num_dimensions == 3) && + "Flood fill is only supported for two and three dimensions"); + mockturtle::stopwatch stop{stats.time_total}; const auto step_point_samples = generate_random_step_points(samples); - // for each sample point in parallel - std::for_each(FICTION_EXECUTION_POLICY_PAR_UNSEQ step_point_samples.cbegin(), step_point_samples.cend(), - [this](const auto& sp) { is_step_point_operational(sp); }); + simulate_operational_status_in_parallel(step_point_samples); - // a queue of (x, y) dimension step points to be evaluated + // a queue of (x, y[, z]) dimension step points to be evaluated std::queue queue{}; // a utility function that adds the adjacent points to the queue for further evaluation const auto queue_next_points = [this, &queue](const step_point& sp) { - for (const auto& m : moore_neighborhood(sp)) + if (num_dimensions == 2) + { + for (const auto& m : moore_neighborhood_2d(sp)) + { + if (!has_already_been_sampled(m)) + { + queue.push(m); + } + } + } + else // num_dimensions == 3 { - if (!has_already_been_sampled(m)) + for (const auto& m : moore_neighborhood_3d(sp)) { - queue.push(m); + if (!has_already_been_sampled(m)) + { + queue.push(m); + } } } }; @@ -543,16 +614,13 @@ class operational_domain_impl [[nodiscard]] operational_domain contour_tracing(const std::size_t samples) noexcept { + assert(num_dimensions == 2 && "Contour tracing is only supported for two dimensions"); + mockturtle::stopwatch stop{stats.time_total}; - // first, perform random sampling to find an operational starting point - const auto starting_point = find_operational_step_point_via_random_sampling(samples); + const auto step_point_samples = generate_random_step_points(samples); - // if no operational point was found within the specified number of samples, return - if (!starting_point.has_value()) - { - return op_domain; - } + simulate_operational_status_in_parallel(step_point_samples); const auto next_clockwise_point = [](std::vector& neighborhood, const step_point& backtrack) noexcept -> step_point @@ -568,41 +636,61 @@ class operational_domain_impl return neighborhood.front(); }; - // find an operational point on the contour starting from the randomly determined starting point - const auto contour_starting_point = find_operational_contour_step_point(*starting_point); + // for each sampled point + for (const auto& starting_point : step_point_samples) + { + // if the current starting point is non-operational, skip to the next one + if (op_domain.operational_values[to_parameter_point(starting_point)] == operational_status::NON_OPERATIONAL) + { + continue; + } + + // if the current step point has been inferred as operational, skip to the next one + if (is_step_point_inferred_operational(starting_point)) + { + continue; + } + + // find an operational point on the contour starting from the randomly determined starting point + const auto contour_starting_point = find_operational_contour_step_point(starting_point); - auto current_contour_point = contour_starting_point; - auto backtrack_point = current_contour_point.x == 0 ? - current_contour_point : - step_point{current_contour_point.x - 1, current_contour_point.y}; + auto current_contour_point = contour_starting_point; - auto current_neighborhood = moore_neighborhood(current_contour_point); + const auto x = current_contour_point.step_values[0]; + const auto y = current_contour_point.step_values[1]; - auto next_point = contour_starting_point; + auto backtrack_point = x == 0 ? current_contour_point : step_point{{x - 1, y}}; - if (!current_neighborhood.empty()) - { - next_point = current_contour_point == backtrack_point ? - current_neighborhood.front() : - next_clockwise_point(current_neighborhood, backtrack_point); - } + auto current_neighborhood = moore_neighborhood_2d(current_contour_point); - while (next_point != contour_starting_point) - { - const auto operational_status = is_step_point_operational(next_point); + auto next_point = contour_starting_point; - if (operational_status == operational_status::OPERATIONAL) + if (!current_neighborhood.empty()) { - backtrack_point = current_contour_point; - current_contour_point = next_point; + next_point = current_contour_point == backtrack_point ? + current_neighborhood.front() : + next_clockwise_point(current_neighborhood, backtrack_point); } - else + + while (next_point != contour_starting_point) { - backtrack_point = next_point; + const auto operational_status = is_step_point_operational(next_point); + + if (operational_status == operational_status::OPERATIONAL) + { + backtrack_point = current_contour_point; + current_contour_point = next_point; + } + else + { + backtrack_point = next_point; + } + + current_neighborhood = moore_neighborhood_2d(current_contour_point); + next_point = next_clockwise_point(current_neighborhood, backtrack_point); } - current_neighborhood = moore_neighborhood(current_contour_point); - next_point = next_clockwise_point(current_neighborhood, backtrack_point); + infer_operational_status_in_enclosing_contour(starting_point); } log_stats(); @@ -610,9 +698,9 @@ class operational_domain_impl return op_domain; } /** - * Performs a grid search over the specified parameter ranges. For each physical - * parameter combination found for which the given CDS is physically valid, it is determined whether the CDS is the - * ground state or the n-th excited state. + * Performs a grid search over the specified parameter ranges. For each physical parameter combination found for + * which the given CDS is physically valid, it is determined whether the CDS is the ground state or the n-th excited + * state. * * @param lyt SiDB cell-level layout that is simulated and compared to the given CDS. * @return All physically valid physical parameters and the excited state number. @@ -624,27 +712,61 @@ class operational_domain_impl mockturtle::stopwatch stop{stats.time_total}; - // step points are analyzed sequentially because the CDS is updated for each step point, so parallelizing may - // result in unexpected behavior. - std::for_each(x_indices.cbegin(), x_indices.cend(), - [this, &lyt](const auto x) - { - std::for_each(y_indices.cbegin(), y_indices.cend(), - [this, &lyt, x](const auto y) { is_step_point_suitable(lyt, {x, y}); }); - }); + // Cartesian product of all step point indices + const auto all_index_combinations = cartesian_combinations(indices); + + // calculate the size of each slice + const auto slice_size = (all_index_combinations.size() + num_threads - 1) / num_threads; + + std::vector threads{}; + threads.reserve(num_threads); + + // launch threads, each with its own slice of random step points + for (auto i = 0ul; i < num_threads; ++i) + { + const auto start = i * slice_size; + const auto end = std::min(start + slice_size, all_index_combinations.size()); + + if (start >= end) + { + break; // no more work to distribute + } + + threads.emplace_back( + [this, &lyt, start, end, &all_index_combinations] + { + for (auto it = all_index_combinations.cbegin() + static_cast(start); + it != all_index_combinations.cbegin() + static_cast(end); ++it) + { + is_step_point_suitable(lyt, step_point{*it}); // construct a step_point + } + }); + } + + // wait for all threads to complete + for (auto& thread : threads) + { + if (thread.joinable()) + { + thread.join(); + } + } sidb_simulation_parameters simulation_parameters = params.simulation_parameters; for (const auto& [param_point, status] : op_domain.operational_values) { - if constexpr (std::is_same_v>) + if constexpr (std::is_same_v>) { if (status == operational_status::NON_OPERATIONAL) { continue; } - set_x_dimension_value(simulation_parameters, param_point.x); - set_y_dimension_value(simulation_parameters, param_point.y); + + for (auto d = 0u; d < num_dimensions; ++d) + { + set_dimension_value(simulation_parameters, param_point.parameters[d], d); + } auto sim_results = sidb_simulation_result{}; @@ -673,13 +795,16 @@ class operational_domain_impl } const auto energy_dist = energy_distribution(sim_results.charge_distributions); + lyt.assign_physical_parameters(simulation_parameters); const auto position = find_key_with_tolerance(energy_dist, lyt.get_system_energy()); + if (position == energy_dist.cend()) { continue; } - const auto excited_state_number = std::distance(energy_dist.begin(), position); + + const auto excited_state_number = std::distance(energy_dist.cbegin(), position); suitable_params_domain.operational_values.emplace(param_point, excited_state_number); } } @@ -705,29 +830,33 @@ class operational_domain_impl */ operational_domain_stats& stats; /** - * The output BDL pair of the layout. + * The output BDL pairs of the layout. */ const std::vector>> output_bdl_pairs; /** - * X dimension steps. + * The number of dimensions. */ - std::vector x_indices; + const std::size_t num_dimensions; /** - * Y dimension steps. + * Dimension steps. */ - std::vector y_indices; + std::vector> indices; /** - * All x dimension values. + * All dimension values. */ - std::vector x_values; + std::vector> values; /** - * All y dimension values. + * The operational domain of the layout. */ - std::vector y_values; + OpDomain op_domain{}; /** - * The operational domain of the layout. + * Forward-declare step_point. */ - OPDomain op_domain{}; + struct step_point; + /** + * All the points inferred (assumed) to be operational but not actually simulated. + */ + phmap::btree_set inferred_op_domain; /** * Number of simulator invocations. */ @@ -736,6 +865,10 @@ class operational_domain_impl * Number of evaluated parameter combinations. */ std::atomic num_evaluated_parameter_combinations{0}; + /** + * Number of available hardware threads. + */ + const std::size_t num_threads{std::thread::hardware_concurrency()}; /** * A step point represents a point in the x and y dimension from 0 to the maximum number of steps. A step point does * not hold the actual parameter values, but the step values in the x and y dimension, respectively. @@ -751,18 +884,13 @@ class operational_domain_impl /** * Standard constructor. * - * @param x_step X dimension step value. - * @param y_step Y dimension step value. - */ - step_point(const std::size_t x_step, const std::size_t y_step) : x{x_step}, y{y_step} {} - /** - * X dimension step value. + * @param steps All dimension step values. */ - std::size_t x; + explicit step_point(const std::vector& steps) : step_values(steps) {} /** - * Y dimension step value. + * All dimension step values. */ - std::size_t y; + std::vector step_values; /** * Equality operator. * @@ -771,7 +899,7 @@ class operational_domain_impl */ [[nodiscard]] bool operator==(const step_point& other) const noexcept { - return x == other.x && y == other.y; + return step_values == other.step_values; } /** * Inequality operator. @@ -791,11 +919,7 @@ class operational_domain_impl */ [[nodiscard]] bool operator<(const step_point& other) const noexcept { - if (y != other.y) - { - return y < other.y; - } - return x < other.x; + return step_values < other.step_values; } }; /** @@ -806,7 +930,13 @@ class operational_domain_impl */ [[nodiscard]] parameter_point to_parameter_point(const step_point& sp) const noexcept { - return {x_values[sp.x], y_values[sp.y]}; + std::vector parameter_values{}; + for (auto d = 0u; d < num_dimensions; ++d) + { + parameter_values.push_back(values[d][sp.step_values[d]]); + } + + return parameter_point{parameter_values}; } /** * Converts a parameter point to a step point. @@ -816,49 +946,35 @@ class operational_domain_impl */ [[nodiscard]] step_point to_step_point(const parameter_point& pp) const noexcept { - const auto it_x = std::lower_bound(x_values.cbegin(), x_values.cend(), pp.x); - const auto it_y = std::lower_bound(y_values.cbegin(), y_values.cend(), pp.y); + std::vector step_values{}; + step_values.reserve(num_dimensions); - assert(it_x != x_values.cend() && "parameter point is outside of the x range"); - assert(it_y != y_values.cend() && "parameter point is outside of the y range"); + for (auto d = 0u; d < num_dimensions; ++d) + { + const auto it = std::lower_bound(values[d].cbegin(), values[d].cend(), pp.parameters[d]); - const auto x_dis = std::distance(x_values.cbegin(), it_x); - const auto y_dis = std::distance(y_values.cbegin(), it_y); + assert(it != values[d].cend() && "parameter point is outside of the value range"); - return {static_cast(x_dis), static_cast(y_dis)}; - } - /** - * Calculates the number of steps in the x dimension based on the provided parameters. - * - * @return The number of steps in the x dimension. - */ - [[nodiscard]] inline std::size_t num_x_steps() const noexcept - { - return static_cast(std::round((params.x_max - params.x_min) / params.x_step)); + const auto dis = std::distance(values[d].cbegin(), it); + + step_values.push_back(static_cast(dis)); + } + + return step_point{step_values}; } /** - * Calculates the number of steps in the y dimension based on the provided parameters. + * Calculates the number of steps in the given dimension based on the provided parameters. * - * @return The number of steps in the y dimension. + * @return The number of steps in the given dimension. */ - [[nodiscard]] inline std::size_t num_y_steps() const noexcept + [[nodiscard]] inline std::size_t num_steps(const std::size_t dimension) const noexcept { - return static_cast(std::round((params.y_max - params.y_min) / params.y_step)); + assert(dimension < num_dimensions && "Invalid dimension"); + + return static_cast( + std::round((params.sweep_dimensions[dimension].max - params.sweep_dimensions[dimension].min) / + params.sweep_dimensions[dimension].step)); } - /** - * Potential sweep dimensions. - */ - enum class sweep_dimension : uint8_t - { - /** - * Sweep dimension X. - */ - X, - /** - * Sweep dimension Y. - */ - Y - }; /** * Helper function that sets the value of a sweep dimension in the simulation parameters. * @@ -867,9 +983,9 @@ class operational_domain_impl * @param dim Sweep dimension to set the value `val` to. */ inline void set_dimension_value(sidb_simulation_parameters& sim_parameters, const double val, - const sweep_dimension dim) const noexcept + const std::size_t dim) const noexcept { - sweep_parameter sweep_parameter = dim == sweep_dimension::X ? params.x_dimension : params.y_dimension; + const sweep_parameter sweep_parameter = params.sweep_dimensions[dim].dimension; switch (sweep_parameter) { @@ -895,50 +1011,30 @@ class operational_domain_impl } } /** - * Helper function that sets the value of the x dimension in the simulation parameters. - * - * @param sim_params Simulation parameter object to set the x dimension value of. - * @param val Value to set the x dimension to. - */ - inline void set_x_dimension_value(sidb_simulation_parameters& sim_params, const double val) const noexcept - { - set_dimension_value(sim_params, val, sweep_dimension::X); - } - /** - * Helper function that sets the value of the y dimension in the simulation parameters. - * - * @param sim_params Simulation parameter object to set the y dimension value of. - * @param val Value to set the y dimension to. - */ - inline void set_y_dimension_value(sidb_simulation_parameters& sim_params, const double val) const noexcept - { - set_dimension_value(sim_params, val, sweep_dimension::Y); - } - /** - * Determines whether the point at step position `(x, y)` has already been sampled and returns the operational value - * at `(x, y)` if it already exists. Here, `x` and `y` represent steps in the x and y dimension, respectively, not + * Determines whether the point at step position `(d1, ..., dn)` has already been sampled and returns the + * operational value at `(d1, ..., dn)` if it already exists. Here, `di` represents steps in the i-th dimension, not * the actual values of the parameters. * * @param sp Step point to check. - * @return The operational status of the point at step position `sp = (x, y)` or `std::nullopt` if `(x, y)` has not - * been sampled yet. + * @return The operational status of the point at step position `sp = (d1, ..., dn)` or `std::nullopt` if the point + * `(d1, ..., dn)` has not been sampled yet. */ [[nodiscard]] inline std::optional has_already_been_sampled(const step_point& sp) const noexcept { - if (const auto it = find_parameter_point_with_tolerance(op_domain.operational_values, to_parameter_point(sp)); - it != op_domain.operational_values.cend()) + if (const auto v = contains_parameter_point(op_domain.operational_values, to_parameter_point(sp)); + v.has_value()) { - return it->second; + return v.value().second; } return std::nullopt; } /** - * Logs and returns the operational status at the given point `sp = (x, y)`. If the point has already been sampled, - * it returns the cached value. Otherwise, a ground state simulation is performed for all input combinations of the - * stored layout using the given simulation parameters. It terminates as soon as a non-operational state is found. - * In the worst case, the function performs \f$2^n\f$ simulations, where \f$n\f$ is the number of inputs of the - * layout. This function is used by all operational domain computation techniques. + * Logs and returns the operational status at the given point `sp = (d1, ..., dn)`. If the point has already been + * sampled, it returns the cached value. Otherwise, a ground state simulation is performed for all input + * combinations of the stored layout using the given simulation parameters. It terminates as soon as a + * non-operational state is found. In the worst case, the function performs \f$2^i\f$ simulations, where \f$i\f$ is + * the number of inputs of the layout. This function is used by all operational domain computation techniques. * * Any investigated point is added to the stored `op_domain`, regardless of its operational status. * @@ -947,38 +1043,39 @@ class operational_domain_impl */ operational_status is_step_point_operational(const step_point& sp) noexcept { - // if the point has already been sampled, return the stored operational status if (const auto op_value = has_already_been_sampled(sp); op_value.has_value()) { return *op_value; } - // fetch the x and y dimension values const auto param_point = to_parameter_point(sp); - const auto operational = [this, ¶m_point]() + const auto operational = [this, ¶m_point]() noexcept { - op_domain.operational_values[param_point] = operational_status::OPERATIONAL; + op_domain.operational_values.try_emplace(param_point, operational_status::OPERATIONAL); return operational_status::OPERATIONAL; }; - const auto non_operational = [this, ¶m_point]() + const auto non_operational = [this, ¶m_point]() noexcept { - op_domain.operational_values[param_point] = operational_status::NON_OPERATIONAL; + op_domain.operational_values.try_emplace(param_point, operational_status::NON_OPERATIONAL); return operational_status::NON_OPERATIONAL; }; - // increment the number of evaluated parameter combinations ++num_evaluated_parameter_combinations; sidb_simulation_parameters sim_params = params.simulation_parameters; - set_x_dimension_value(sim_params, param_point.x); - set_y_dimension_value(sim_params, param_point.y); + + for (auto d = 0u; d < num_dimensions; ++d) + { + set_dimension_value(sim_params, values[d][sp.step_values[d]], d); + } const auto& [status, sim_calls] = is_operational(layout, truth_table, is_operational_params{sim_params, params.sim_engine}); + num_simulator_invocations += sim_calls; if (status == operational_status::NON_OPERATIONAL) @@ -986,7 +1083,6 @@ class operational_domain_impl return non_operational(); } - // if we made it here, the layout is operational return operational(); } /** @@ -997,7 +1093,7 @@ class operational_domain_impl * @param sp Step point to be investigated. * @return The operational status of the layout under the given simulation parameters. */ - operational_status is_step_point_suitable(Lyt& lyt, const step_point& sp) noexcept + operational_status is_step_point_suitable(Lyt lyt, const step_point& sp) noexcept { // if the point has already been sampled, return the stored operational status if (const auto op_value = has_already_been_sampled(sp); op_value.has_value()) @@ -1010,14 +1106,14 @@ class operational_domain_impl const auto operational = [this, ¶m_point]() { - op_domain.operational_values[param_point] = operational_status::OPERATIONAL; + op_domain.operational_values.try_emplace(param_point, operational_status::OPERATIONAL); return operational_status::OPERATIONAL; }; const auto non_operational = [this, ¶m_point]() { - op_domain.operational_values[param_point] = operational_status::NON_OPERATIONAL; + op_domain.operational_values.try_emplace(param_point, operational_status::NON_OPERATIONAL); return operational_status::NON_OPERATIONAL; }; @@ -1026,8 +1122,11 @@ class operational_domain_impl ++num_evaluated_parameter_combinations; sidb_simulation_parameters sim_params = params.simulation_parameters; - set_x_dimension_value(sim_params, param_point.x); - set_y_dimension_value(sim_params, param_point.y); + + for (auto d = 0u; d < num_dimensions; ++d) + { + set_dimension_value(sim_params, param_point.parameters[d], d); + } lyt.assign_physical_parameters(sim_params); @@ -1040,34 +1139,112 @@ class operational_domain_impl return non_operational(); } /** - * Generates (potentially repeating) random `step_points` in the stored parameter range. The number of generated - * points is exactly equal to `samples`. + * Checks whether the given step point is part of the inferred operational domain. If it is, the point is marked as + * enclosed in the operational domain. No simulation is performed on `sp`. If `sp` is not contained in the inferred + * operational domain, it does not mean that `sp` is definitely non-operational. It could still appear in the + * regular operational domain with either status. + * + * This function is used by the contour tracing algorithm. + * + * @param sp Step point to check for inferred operational status. + * @return `true` iff `sp` is contained in `inferred_op_domain`. + */ + [[nodiscard]] inline bool is_step_point_inferred_operational(const step_point& sp) const noexcept + { + return inferred_op_domain.count(sp) > 0; + } + /** + * Generates unique random `step_points` in the stored parameter range. The number of generated points is at most + * equal to `samples`. * - * @param samples Number of random `step_point`s to generate. - * @return A set of random `step_point`s in the stored parameter range. + * @param samples Maximum number of random `step_point`s to generate. + * @return A vector of unique random `step_point`s in the stored parameter range of size at most equal to `samples`. */ - [[nodiscard]] std::set generate_random_step_points(const std::size_t samples) noexcept + [[nodiscard]] std::vector generate_random_step_points(const std::size_t samples) const noexcept { static std::mt19937_64 generator{std::random_device{}()}; // instantiate distributions - std::uniform_int_distribution x_distribution{0, x_indices.size() - 1}; - std::uniform_int_distribution y_distribution{0, y_indices.size() - 1}; + std::vector> distributions{}; + distributions.reserve(num_dimensions); + + for (auto d = 0u; d < num_dimensions; ++d) + { + distributions.emplace_back(0, indices[d].size() - 1); + } // container for the random samples - std::set step_point_samples{}; + phmap::btree_set step_point_samples{}; for (std::size_t i = 0; i < samples; ++i) { - // sample x and y dimension - step_point_samples.insert(step_point{x_distribution(generator), y_distribution(generator)}); + std::vector dimension_samples{}; + dimension_samples.reserve(num_dimensions); + + // sample all dimensions + for (auto d = 0u; d < num_dimensions; ++d) + { + dimension_samples.push_back(distributions[d](generator)); + } + + step_point_samples.insert(step_point{dimension_samples}); } - return step_point_samples; + return std::vector(step_point_samples.cbegin(), step_point_samples.cend()); + } + /** + * Simulates the operational status of the given points in parallel. It divides the work among multiple threads to + * speed up the computation. + * + * @note The distribution of the work among threads is a simple slice-based approach. If your step points are + * ordered, consider shuffling the vector first for better load balancing. Otherwise, some threads might finish + * early if they got assigned a slice with mainly non-operational samples, which are faster to compute due to the + * early termination condition. + * + * @param step_points A vector of step points for which the operational status is to be simulated. + */ + void simulate_operational_status_in_parallel(const std::vector& step_points) noexcept + { + // calculate the size of each slice + const auto slice_size = (step_points.size() + num_threads - 1) / num_threads; + + std::vector threads{}; + threads.reserve(num_threads); + + // launch threads, each with its own slice of random step points + for (auto i = 0ul; i < num_threads; ++i) + { + const auto start = i * slice_size; + const auto end = std::min(start + slice_size, step_points.size()); + + if (start >= end) + { + break; // no more work to distribute + } + + threads.emplace_back( + [this, start, end, &step_points] + { + for (auto it = step_points.cbegin() + static_cast(start); + it != step_points.cbegin() + static_cast(end); ++it) + { + is_step_point_operational(*it); + } + }); + } + + // wait for all threads to complete + for (auto& thread : threads) + { + if (thread.joinable()) + { + thread.join(); + } + } } /** * Performs random sampling to find any operational parameter combination. This function is useful if a single - * starting point is required within the domain to expand from. This function returns the step in x and y dimension + * starting point is required within the domain to expand from. This function returns the step in all dimensions * of the first operational point found. If no operational parameter combination can be found within the given * number of samples, the function returns `std::nullopt`. * @@ -1076,7 +1253,7 @@ class operational_domain_impl * @param samples Maximum number of samples to take. Works as a timeout. * @return The first operational step point, if any could be found, `std::nullopt` otherwise. */ - [[nodiscard]] std::optional + [[maybe_unused]] [[nodiscard]] std::optional find_operational_step_point_via_random_sampling(const std::size_t samples) noexcept { for (const auto& sample_step_point : generate_random_step_points(samples)) @@ -1084,7 +1261,7 @@ class operational_domain_impl // determine the operational status const auto operational_value = is_step_point_operational(sample_step_point); - // if the parameter combination is operational, return its step values in x and y dimension + // if the parameter combination is operational, return its step values in all dimensions if (operational_value == operational_status::OPERATIONAL) { return sample_step_point; @@ -1105,12 +1282,17 @@ class operational_domain_impl */ [[nodiscard]] step_point find_operational_contour_step_point(const step_point& starting_point) noexcept { + assert(num_dimensions == 2 && "Contour tracing is only supported for two dimensions"); + assert(starting_point.step_values.size() == 2 && "Given step point must have 2 dimensions"); + auto latest_operational_point = starting_point; // move towards the left border of the parameter range - for (std::size_t x = starting_point.x; x > 0; --x) + for (std::size_t x = starting_point.step_values[0]; x > 0; --x) { - const auto left_step = step_point{x, starting_point.y}; + const auto y = starting_point.step_values[1]; + + const auto left_step = step_point{{x, y}}; const auto operational_status = is_step_point_operational(left_step); @@ -1124,76 +1306,228 @@ class operational_domain_impl } } - // if no boundary point was found, the operational area extends outside the parameter range + // if no boundary point was found, the operational area extends outside the parameter range; // return the latest operational point return latest_operational_point; } /** - * Returns the Moore neighborhood of the step point at `sp = (x, y)`. The Moore neighborhood is the set of all - * points that are adjacent to `(x, y)` including the diagonals. Thereby, the Moore neighborhood contains up to 8 - * points as points outside of the parameter range are not gathered. The points are returned in clockwise order - * starting from the right neighbor. + * Returns the 2D Moore neighborhood of the step point at `sp = (x, y)`. The 2D Moore neighborhood is the set of all + * points that are adjacent to `(x, y)` in the plane including the diagonals. Thereby, the 2D Moore neighborhood + * contains up to 8 points as points outside of the parameter range are not gathered. The points are returned in + * clockwise order starting from the right neighbor. * - * @param sp Step point to get the Moore neighborhood of. - * @return The Moore neighborhood of the step point at `sp = (x, y)`. + * @param sp Step point to get the 2D Moore neighborhood of. + * @return The 2D Moore neighborhood of the step point at `sp = (x, y)`. */ - [[nodiscard]] std::vector moore_neighborhood(const step_point& sp) const noexcept + [[nodiscard]] std::vector moore_neighborhood_2d(const step_point& sp) const noexcept { + assert(num_dimensions == 2 && "2D Moore neighborhood is only supported for 2 dimensions"); + assert(sp.step_values.size() == 2 && "Given step point must have 2 dimensions"); + std::vector neighbors{}; neighbors.reserve(8); - const auto& [x, y] = sp; + const auto emplace = [&neighbors](const auto x, const auto y) noexcept + { neighbors.emplace_back(std::vector{x, y}); }; + + const auto x = sp.step_values[0]; + const auto y = sp.step_values[1]; + + const auto num_x_indices = indices[0].size(); + const auto num_y_indices = indices[1].size(); const auto decr_x = (x > 0) ? x - 1 : x; - const auto incr_x = (x + 1 < x_indices.size()) ? x + 1 : x; + const auto incr_x = (x + 1 < num_x_indices) ? x + 1 : x; const auto decr_y = (y > 0) ? y - 1 : y; - const auto incr_y = (y + 1 < y_indices.size()) ? y + 1 : y; + const auto incr_y = (y + 1 < num_y_indices) ? y + 1 : y; // add neighbors in clockwise direction // right if (x != incr_x) { - neighbors.emplace_back(incr_x, y); + emplace(incr_x, y); } // lower-right if (x != incr_x && y != decr_y) { - neighbors.emplace_back(incr_x, decr_y); + emplace(incr_x, decr_y); } // down if (y != decr_y) { - neighbors.emplace_back(x, decr_y); + emplace(x, decr_y); } // lower-left if (x != decr_x && y != decr_y) { - neighbors.emplace_back(decr_x, decr_y); + emplace(decr_x, decr_y); } // left if (x != decr_x) { - neighbors.emplace_back(decr_x, y); + emplace(decr_x, y); } // upper-left if (x != decr_x && y != incr_y) { - neighbors.emplace_back(decr_x, incr_y); + emplace(decr_x, incr_y); } // up if (y != incr_y) { - neighbors.emplace_back(x, incr_y); + emplace(x, incr_y); } // upper-right if (x != incr_x && y != incr_y) { - neighbors.emplace_back(incr_x, incr_y); + emplace(incr_x, incr_y); } return neighbors; }; + /** + * Returns the 3D Moore neighborhood of the step point at `sp = (x, y, z)`. The 3D Moore neighborhood is the set of + * all points that are adjacent to `(x, y, z)` in the 3D space including the diagonals. Thereby, the 3D Moore + * neighborhood contains up to 26 points as points outside of the parameter range are not gathered. The points are + * returned in no particular order. + * + * @param sp Step point to get the 3D Moore neighborhood of. + * @return The 3D Moore neighborhood of the step point at `sp = (x, y, z)`. + */ + [[nodiscard]] std::vector moore_neighborhood_3d(const step_point& sp) const noexcept + { + assert(num_dimensions == 3 && "3D Moore neighborhood is only supported for 3 dimensions"); + assert(sp.step_values.size() == 3 && "Given step point must have 3 dimensions"); + + std::vector neighbors{}; + neighbors.reserve(26); + + const auto emplace = [&neighbors](const auto x, const auto y, const auto z) noexcept + { neighbors.emplace_back(std::vector{x, y, z}); }; + + const auto x = sp.step_values[0]; + const auto y = sp.step_values[1]; + const auto z = sp.step_values[2]; + + const auto num_x_indices = indices[0].size(); + const auto num_y_indices = indices[1].size(); + const auto num_z_indices = indices[2].size(); + + // add neighbors in no particular order + + // iterate over all combinations of (-1, 0, 1) offsets for x, y, and z + for (const int64_t x_offset : {-1, 0, 1}) + { + for (const int64_t y_offset : {-1, 0, 1}) + { + for (const int64_t z_offset : {-1, 0, 1}) + { + // skip the center cell + if (x_offset == 0 && y_offset == 0 && z_offset == 0) + { + continue; + } + + // calculate new coordinate + const int64_t dx = static_cast(x) + x_offset; + const int64_t dy = static_cast(y) + y_offset; + const int64_t dz = static_cast(z) + z_offset; + + // check if the new coordinate is within the bounds + if ((dx >= 0 && dx < static_cast(num_x_indices)) && + (dy >= 0 && dy < static_cast(num_y_indices)) && + (dz >= 0 && dz < static_cast(num_z_indices))) + { + emplace(static_cast(dx), static_cast(dy), static_cast(dz)); + } + } + } + } + + return neighbors; + } + /** + * Given a starting point, this function marks all points that are enclosed by the operational domain contour as + * 'inferred operational'. This assumes that the operational domain does not have holes. To the best of the author's + * knowledge, at the time of writing this code, there exists no proof that operational domains are always + * continuous, i.e., hole-free. Marking points as 'inferred operational' can be useful to avoid recomputation in, + * e.g., contour tracing if an operational domain with multiple islands is investigated. + * + * The function starts at the given starting point and performs flood fill to mark all points that are reachable + * from the starting point until it encounters the non-operational edges. + * + * Note that no physical simulation is conducted by this function! + * + * @param starting_point Step point at which to start the inference. If `starting_point` is non-operational, this + * function might invoke undefined behavior. + */ + void infer_operational_status_in_enclosing_contour(const step_point& starting_point) noexcept + { + assert(num_dimensions == 2 && "This function is only supported for two dimensions"); + assert(is_step_point_operational(starting_point) == operational_status::OPERATIONAL && + "starting_point must be within the operational domain"); + + // a queue of (x, y) dimension step points to be marked as inferred operational + std::queue queue{}; + + // a utility function that adds the adjacent points to the queue for further evaluation + const auto queue_next_points = [this, &queue](const step_point& sp) noexcept + { + for (const auto& m : moore_neighborhood_2d(sp)) + { + // if the point has already been inferred as operational, continue with the next + if (is_step_point_inferred_operational(m)) + { + continue; + } + + // if the point has already been sampled + if (const auto operational_status = has_already_been_sampled(m); operational_status.has_value()) + { + // and found to be non-operational, continue with the next + if (operational_status.value() == operational_status::NON_OPERATIONAL) + { + continue; + } + } + + // otherwise, it is either found operational or can be inferred as such + queue.push(m); + inferred_op_domain.insert(m); + } + }; + + // if the starting point has not already been inferred as operational + if (is_step_point_inferred_operational(starting_point)) + { + // mark the starting point as inferred operational + inferred_op_domain.insert(starting_point); + + // add the starting point's neighbors to the queue + queue_next_points(starting_point); + } + + // for each point in the queue + while (!queue.empty()) + { + // fetch the step point and remove it from the queue + const auto sp = queue.front(); + queue.pop(); + + // if the point is known to be non-operational continue with the next + if (const auto operational_status = has_already_been_sampled(sp); operational_status.has_value()) + { + if (operational_status.value() == operational_status::NON_OPERATIONAL) + { + continue; + } + } + + // otherwise (operational or unknown), queue its neighbors + queue_next_points(sp); + } + } /** * Helper function that writes the the statistics of the operational domain computation to the statistics object. * Due to data races that can occur during the computation, each value is temporarily held in an atomic variable and @@ -1201,8 +1535,8 @@ class operational_domain_impl */ void log_stats() const noexcept { - stats.num_simulator_invocations = num_simulator_invocations; - stats.num_evaluated_parameter_combinations = num_evaluated_parameter_combinations; + stats.num_simulator_invocations = num_simulator_invocations.load(); + stats.num_evaluated_parameter_combinations = num_evaluated_parameter_combinations.load(); for (const auto& [param_point, status] : op_domain.operational_values) { @@ -1233,6 +1567,8 @@ class operational_domain_impl * state simulations, where \f$n\f$ is the number of inputs of the layout. Each exact ground state simulation has * exponential complexity in of itself. Therefore, the algorithm is only feasible for small layouts with few inputs. * + * This function may throw an `std::invalid_argument` exception if the given sweep parameters are invalid. + * * @tparam Lyt SiDB cell-level layout type. * @tparam TT Truth table type. * @param lyt Layout to compute the operational domain for. @@ -1251,6 +1587,9 @@ operational_domain_grid_search(const Lyt& lyt, const std::vector& spec, static_assert(has_sidb_technology_v, "Lyt is not an SiDB layout"); static_assert(kitty::is_truth_table::value, "TT is not a truth table"); + // this may throw an `std::invalid_argument` exception + detail::validate_sweep_parameters(params); + operational_domain_stats st{}; detail::operational_domain_impl> p{lyt, spec, params, st}; @@ -1276,6 +1615,8 @@ operational_domain_grid_search(const Lyt& lyt, const std::vector& spec, * ground state simulations, where \f$n\f$ is the number of inputs of the layout. Each exact ground state simulation * has exponential complexity in of itself. Therefore, the algorithm is only feasible for small layouts with few inputs. * + * This function may throw an `std::invalid_argument` exception if the given sweep parameters are invalid. + * * @tparam Lyt SiDB cell-level layout type. * @tparam TT Truth table type. * @param lyt Layout to compute the operational domain for. @@ -1295,6 +1636,9 @@ operational_domain_random_sampling(const Lyt& lyt, const std::vector& spec, static_assert(has_sidb_technology_v, "Lyt is not an SiDB layout"); static_assert(kitty::is_truth_table::value, "TT is not a truth table"); + // this may throw an `std::invalid_argument` exception + detail::validate_sweep_parameters(params); + operational_domain_stats st{}; detail::operational_domain_impl> p{lyt, spec, params, st}; @@ -1315,9 +1659,9 @@ operational_domain_random_sampling(const Lyt& lyt, const std::vector& spec, * inputs of the truth table. * * This algorithm first uses random sampling to find several operational points within the parameter range. From there, - * it employs the "flood fill" algorithm to explore the operational domain. The algorithm is guaranteed to find all - * operational areas in their entirety if the initial random sampling found at least one operational point within them. - * Thereby, this algorithm works for disconnected operational domains. + * it employs the "flood fill" algorithm to explore the operational domain. The algorithm is guaranteed to determine all + * operational "islands" in their entirety if the initial random sampling found at least one operational point within + * them. Thereby, this algorithm works for disconnected operational domains. * * It performs `samples` uniformly-distributed random samples within the parameter range. From there, it performs * another number of samples equal to the number of points within the operational domain plus the first non-operational @@ -1330,6 +1674,8 @@ operational_domain_random_sampling(const Lyt& lyt, const std::vector& spec, * Computation in Silicon Dangling Bond Logic\" by M. Walter, J. Drewniok, S. S. H. Ng, K. Walus, and R. Wille in * NANOARCH 2023. * + * This function may throw an `std::invalid_argument` exception if the given sweep parameters are invalid. + * * @tparam Lyt SiDB cell-level layout type. * @tparam TT Truth table type. * @param lyt Layout to compute the operational domain for. @@ -1348,6 +1694,14 @@ operational_domain_flood_fill(const Lyt& lyt, const std::vector& spec, const static_assert(has_sidb_technology_v, "Lyt is not an SiDB layout"); static_assert(kitty::is_truth_table::value, "TT is not a truth table"); + if (params.sweep_dimensions.size() != 2 && params.sweep_dimensions.size() != 3) + { + throw std::invalid_argument("Flood fill is only applicable to 2 or 3 dimensions"); + } + + // this may throw an `std::invalid_argument` exception + detail::validate_sweep_parameters(params); + operational_domain_stats st{}; detail::operational_domain_impl> p{lyt, spec, params, st}; @@ -1367,15 +1721,16 @@ operational_domain_flood_fill(const Lyt& lyt, const std::vector& spec, const * implementing the given truth table. The input BDL pairs of the layout are assumed to be in the same order as the * inputs of the truth table. * - * This algorithm first uses random sampling to find a single operational point within the parameter range. From there, + * This algorithm first uses random sampling to find a set of operational point within the parameter range. From there, * it traverses outwards to find the edge of the operational area and performs Moore neighborhood contour tracing to - * explore the contour of the operational domain. If the operational domain is connected, the algorithm is guaranteed to - * find the contours of the entire operational domain within the parameter range if the initial random sampling found an - * operational point. + * explore the contour of the operational domain. This is repeated for all initially sampled points that do not lie + * within a contour. The algorithm is guaranteed to determine the contours of all operational "islands" if the initial + * random sampling found at least one operational point within them. Thereby, this algorithm works for disconnected + * operational domains. * - * It performs up to `samples` uniformly-distributed random samples within the parameter range until an operational - * point is found. From there, it performs another number of samples equal to the distance to an edge of the operational - * area. Finally, it performs up to 8 samples for each contour point (however, the actual number is usually much lower). + * It performs `samples` uniformly-distributed random samples within the parameter range. For each thusly discovered + * operational island, it performs another number of samples equal to the distance to an edge of each operational + * area. Finally, it performs up to 8 samples for each contour point (however, the actual number is usually lower). * For each sample, the algorithm performs one operational check on the layout, where each operational check consists of * up to \f$2^n\f$ exact ground state simulations, where \f$n\f$ is the number of inputs of the layout. Each exact * ground state simulation has exponential complexity in of itself. Therefore, the algorithm is only feasible for small @@ -1385,6 +1740,8 @@ operational_domain_flood_fill(const Lyt& lyt, const std::vector& spec, const * Computation in Silicon Dangling Bond Logic\" by M. Walter, J. Drewniok, S. S. H. Ng, K. Walus, and R. Wille in * NANOARCH 2023. * + * This function may throw an `std::invalid_argument` exception if the given sweep parameters are invalid. + * * @tparam Lyt SiDB cell-level layout type. * @tparam TT Truth table type. * @param lyt Layout to compute the operational domain for. @@ -1404,6 +1761,14 @@ operational_domain_contour_tracing(const Lyt& lyt, const std::vector& spec, static_assert(has_sidb_technology_v, "Lyt is not an SiDB layout"); static_assert(kitty::is_truth_table::value, "TT is not a truth table"); + if (params.sweep_dimensions.size() != 2) + { + throw std::invalid_argument("Contour tracing is only applicable to exactly 2 dimensions"); + } + + // this may throw an `std::invalid_argument` exception + detail::validate_sweep_parameters(params); + operational_domain_stats st{}; detail::operational_domain_impl> p{lyt, spec, params, st}; @@ -1422,24 +1787,34 @@ operational_domain_contour_tracing(const Lyt& lyt, const std::vector& spec, namespace std { + // make `operational_domain::parameter_point` compatible with `std::integral_constant` template <> struct tuple_size : std::integral_constant {}; + // make `operational_domain::parameter_point` compatible with `std::tuple_element` template struct tuple_element { using type = double; }; + // make `operational_domain::parameter_point` compatible with `std::hash` template <> struct hash { + // tolerance for double hashing + static constexpr auto tolerance = fiction::physical_constants::POP_STABILITY_ERR; + size_t operator()(const fiction::parameter_point& p) const noexcept { size_t h = 0; - fiction::hash_combine(h, p.x, p.y); + for (const auto& d : p.parameters) + { + // hash the double values with tolerance + fiction::hash_combine(h, static_cast(d / tolerance)); + } return h; } diff --git a/include/fiction/io/print_layout.hpp b/include/fiction/io/print_layout.hpp index c2f442987..c216de2dc 100644 --- a/include/fiction/io/print_layout.hpp +++ b/include/fiction/io/print_layout.hpp @@ -465,9 +465,22 @@ void print_sidb_layout(std::ostream& os, const Lyt& lyt, const bool lat_color = } } - if (!already_printed && lyt.get_cell_type(loop_coordinate) != sidb_technology::cell_type::EMPTY) + if (const auto ct = lyt.get_cell_type(loop_coordinate); + ct != sidb_technology::cell_type::EMPTY && !already_printed) { - os << fmt::format(lat_color ? detail::SIDB_DEF_NEU_COLOR : detail::NO_COLOR, " â—¯ "); + if (ct == sidb_technology::cell_type::INPUT) + { + os << fmt::format(lat_color ? detail::INP_COLOR : detail::NO_COLOR, " â—¯ "); + } + else if (ct == sidb_technology::cell_type::OUTPUT) + { + os << fmt::format(lat_color ? detail::OUT_COLOR : detail::NO_COLOR, " â—¯ "); + } + else // NORMAL cell + { + os << fmt::format(lat_color ? detail::SIDB_DEF_NEU_COLOR : detail::NO_COLOR, " â—¯ "); + } + already_printed = true; } diff --git a/include/fiction/io/write_operational_domain.hpp b/include/fiction/io/write_operational_domain.hpp index 7d2cb9549..5d80c42a7 100644 --- a/include/fiction/io/write_operational_domain.hpp +++ b/include/fiction/io/write_operational_domain.hpp @@ -9,8 +9,10 @@ #include "fiction/algorithms/simulation/sidb/operational_domain.hpp" #include "fiction/io/csv_writer.hpp" +#include #include #include +#include #include #include @@ -22,14 +24,34 @@ namespace fiction */ struct write_operational_domain_params { + /** + * Mode selector for writing samples to file. + */ + enum class sample_writing_mode : uint8_t + { + /** + * Write all samples, including non-operational ones. This may lead to large file sizes. + */ + ALL_SAMPLES, + /** + * Write operational samples only. This can drastically reduce file size and help with visibility in 3D plots. + */ + OPERATIONAL_ONLY + }; /** * The tag used to represent the operational value of a parameter set. */ - std::string_view operational_tag = "operational"; + std::string_view operational_tag = "1"; /** * The tag used to represent the non-operational value of a parameter set. */ - std::string_view non_operational_tag = "non-operational"; + std::string_view non_operational_tag = "0"; + /** + * Whether to write non-operational samples to the CSV file. If set to `OPERATIONAL_ONLY`, operational samples are + * written exclusively. This yields a significantly smaller CSV file. It is recommended to set this option for 3D + * plots because the non-operational samples would shadow the operational samples anyway. + */ + sample_writing_mode writing_mode = sample_writing_mode::ALL_SAMPLES; }; namespace detail @@ -68,9 +90,15 @@ namespace detail * Writes a CSV representation of an operational domain to the specified output stream. The data are written * as rows, each corresponding to one set of simulation parameters and their corresponding operational status. * - * The output CSV format is as follows: - * X_DIMENSION, Y_DIMENSION, OPERATIONAL STATUS - * ... subsequent rows for each set of simulation parameters. + * The output CSV format is e.g. as follows: + \verbatim embed:rst + .. code-block:: RST + + epsilon_r, lambda_tf, operational status + 0.0, 0.0, 0 + 0.1, 0.0, 1 + ... subsequent rows for each set of simulation parameters + \endverbatim * * The operational status is a binary value represented by specified tags in `params` indicating whether the simulation * parameters are within the operational domain or not. @@ -86,23 +114,68 @@ inline void write_operational_domain(const operational_domain 3) + { + throw std::invalid_argument("unsupported number of dimensions in the given operational domain"); + } + + if (num_dimensions == 1) + { + writer.write_line(detail::sweep_parameter_to_string(opdom.dimensions[0]), "operational status"); + } + else if (num_dimensions == 2) + { + writer.write_line(detail::sweep_parameter_to_string(opdom.dimensions[0]), + detail::sweep_parameter_to_string(opdom.dimensions[1]), "operational status"); + } + else // num_dimensions == 3 + { + writer.write_line(detail::sweep_parameter_to_string(opdom.dimensions[0]), + detail::sweep_parameter_to_string(opdom.dimensions[1]), + detail::sweep_parameter_to_string(opdom.dimensions[2]), "operational status"); + } for (const auto& [sim_param, op_val] : opdom.operational_values) { - writer.write_line(sim_param.x, sim_param.y, - op_val == operational_status::OPERATIONAL ? params.operational_tag : - params.non_operational_tag); + // skip non-operational samples if the respective flag is set + if (params.writing_mode == write_operational_domain_params::sample_writing_mode::OPERATIONAL_ONLY && + op_val == operational_status::NON_OPERATIONAL) + { + continue; + } + + const auto tag = + op_val == operational_status::OPERATIONAL ? params.operational_tag : params.non_operational_tag; + + if (num_dimensions == 1) + { + writer.write_line(sim_param.parameters[0], tag); + } + else if (num_dimensions == 2) + { + writer.write_line(sim_param.parameters[0], sim_param.parameters[1], tag); + } + else // num_dimensions == 3 + { + writer.write_line(sim_param.parameters[0], sim_param.parameters[1], sim_param.parameters[2], tag); + } } } /** * Writes a CSV representation of an operational domain to the specified file. The data are written as rows, each * corresponding to one set of simulation parameters and their corresponding operational status. * - * The output CSV format is as follows: - * X_DIMENSION, Y_DIMENSION, OPERATIONAL STATUS - * ... subsequent rows for each set of simulation parameters. + * The output CSV format is e.g. as follows: + \verbatim embed:rst + .. code-block:: RST + + epsilon_r, lambda_tf, operational status + 0.0, 0.0, 0 + 0.1, 0.0, 1 + ... subsequent rows for each set of simulation parameters + \endverbatim * * The operational status is a binary value represented by specified tags in `params` indicating whether the simulation * parameters are within the operational domain or not. diff --git a/include/fiction/utils/hash.hpp b/include/fiction/utils/hash.hpp index 24ad5710e..21744f073 100644 --- a/include/fiction/utils/hash.hpp +++ b/include/fiction/utils/hash.hpp @@ -6,6 +6,7 @@ #define FICTION_HASH_HPP #include +#include #include #include #include diff --git a/include/fiction/utils/layout_utils.hpp b/include/fiction/utils/layout_utils.hpp index bba266c80..cc5e79514 100644 --- a/include/fiction/utils/layout_utils.hpp +++ b/include/fiction/utils/layout_utils.hpp @@ -477,6 +477,7 @@ template lyt_surface.assign_sidb_defect(siqad::to_fiction_coord>(cd.first), lyt.get_sidb_defect(cd.first)); }); + return lyt_surface; } else if constexpr (is_charge_distribution_surface_v && !is_sidb_defect_surface_v) diff --git a/include/fiction/utils/math_utils.hpp b/include/fiction/utils/math_utils.hpp index 83ed3bd9f..22ecf124e 100644 --- a/include/fiction/utils/math_utils.hpp +++ b/include/fiction/utils/math_utils.hpp @@ -26,14 +26,13 @@ namespace fiction * @return The number rounded to n decimal places. */ template -T round_to_n_decimal_places(const T number, const uint64_t n) noexcept +[[nodiscard]] inline T round_to_n_decimal_places(const T number, const uint64_t n) noexcept { static_assert(std::is_arithmetic_v, "T is not a number type"); const auto factor = std::pow(10.0, static_cast(n)); return static_cast(std::round(static_cast(number) * factor) / factor); } - /** * Takes the absolute value of an integral number if it is signed, and otherwise computes the identity. This avoids a * compiler warning when taking the absolute value of an unsigned number. @@ -42,7 +41,7 @@ T round_to_n_decimal_places(const T number, const uint64_t n) noexcept * @return |n|. */ template -T integral_abs(const T n) noexcept +[[nodiscard]] inline T integral_abs(const T n) noexcept { static_assert(std::is_integral_v, "T is not an integral number type"); @@ -53,7 +52,6 @@ T integral_abs(const T n) noexcept return static_cast(std::abs(static_cast(n))); // needed to solve ambiguity of std::abs } - /** * Calculates the binomial coefficient \f$\binom{n}{k}\f$. * @@ -67,15 +65,19 @@ T integral_abs(const T n) noexcept { return 0; } + uint64_t result = 1; + if (2 * k > n) { k = n - k; } + for (uint64_t i = 1; i <= k; i++) { result = result * (n + 1 - i) / i; } + return result; } @@ -92,6 +94,12 @@ T integral_abs(const T n) noexcept [[nodiscard]] inline std::vector> determine_all_combinations_of_distributing_k_entities_on_n_positions(const std::size_t k, const std::size_t n) noexcept { + // Handle a special case + if (k > n) + { + return {}; + } + std::vector> all_combinations{}; all_combinations.reserve(binomial_coefficient(n, k)); @@ -117,6 +125,43 @@ determine_all_combinations_of_distributing_k_entities_on_n_positions(const std:: return all_combinations; } +/** + * This function computes the Cartesian product of a list of vectors. Each vector in the input list + * represents a dimension, and the function produces all possible combinations where each combination + * consists of one element from each dimension vector. + * + * @tparam VectorDataType The type of elements in the vectors. + * @param sets The sets to compute the Cartesian product for. In this implementation, a vector of vectors is utilized + * for efficiency. Each inner vector represents one dimension. The function generates combinations using one element + * from each dimension vector. + * @return A vector of vectors, where each inner vector represents a combination of elements, one from each dimension. + * The total number of combinations is the product of the sizes of the input vectors. + */ +template +[[nodiscard]] inline std::vector> +cartesian_combinations(const std::vector>& sets) noexcept +{ + std::vector> all_combinations{{}}; + + for (const auto& dimension : sets) + { + std::vector> expanded_products{}; + expanded_products.reserve(all_combinations.size() * dimension.size()); + + for (const auto& product : all_combinations) + { + for (const auto& element : dimension) + { + std::vector new_product = product; + new_product.push_back(element); + expanded_products.push_back(new_product); + } + } + all_combinations = expanded_products; + } + + return all_combinations; // Return the final list of combinations +} } // namespace fiction diff --git a/include/fiction/utils/phmap_utils.hpp b/include/fiction/utils/phmap_utils.hpp index 2c16a908e..cf3aadec0 100644 --- a/include/fiction/utils/phmap_utils.hpp +++ b/include/fiction/utils/phmap_utils.hpp @@ -6,6 +6,7 @@ #define FICTION_PHMAP_UTILS_HPP #include +#include #include #include diff --git a/test/algorithms/iter/bdl_input_iterator.cpp b/test/algorithms/iter/bdl_input_iterator.cpp index b8cedb737..35c928665 100644 --- a/test/algorithms/iter/bdl_input_iterator.cpp +++ b/test/algorithms/iter/bdl_input_iterator.cpp @@ -5,14 +5,11 @@ #include #include -#include #include #include -#include #include #include -#include #include #include @@ -113,16 +110,20 @@ TEST_CASE("Empty layout iteration", "[bdl-input-iterator]") bdl_input_iterator bii{lyt}; + CHECK(bii.num_input_pairs() == 0); CHECK((*bii).num_cells() == 0); // increment ++bii; + CHECK(bii.num_input_pairs() == 0); CHECK((*bii).num_cells() == 0); auto bii_cp = bii++; + CHECK(bii.num_input_pairs() == 0); + CHECK(bii_cp.num_input_pairs() == 0); CHECK((*bii).num_cells() == 0); CHECK((*bii_cp).num_cells() == 0); @@ -130,12 +131,15 @@ TEST_CASE("Empty layout iteration", "[bdl-input-iterator]") --bii; + CHECK(bii.num_input_pairs() == 0); CHECK((*bii).num_cells() == 0); auto bii_cm = bii--; + CHECK(bii.num_input_pairs() == 0); CHECK((*bii).num_cells() == 0); + CHECK(bii_cm.num_input_pairs() == 0); CHECK((*bii_cm).num_cells() == 0); } @@ -161,6 +165,10 @@ TEST_CASE("BDL wire iteration", "[bdl-input-iterator]") bdl_input_iterator bii{lat}; + CHECK((*bii).num_cells() == 7); // 2 inputs (1 already deleted for input pattern 0), 4 normal, 2 outputs + + CHECK(bii.num_input_pairs() == 1); + CHECK(bii == 0ull); // start by incrementing over all input states @@ -235,7 +243,7 @@ TEST_CASE("SiQAD's AND gate iteration", "[bdl-input-iterator]") const sidb_100_cell_clk_lyt_siqad lat{lyt}; - SECTION("siqad coordinates") + SECTION("SiQAD coordinates") { bdl_input_iterator bii{lat}; diff --git a/test/algorithms/mockturtle.cpp b/test/algorithms/mockturtle.cpp index 006178988..f1d6dcbfc 100644 --- a/test/algorithms/mockturtle.cpp +++ b/test/algorithms/mockturtle.cpp @@ -4,6 +4,7 @@ #include +#include "fiction/layouts/coordinates.hpp" #include "utils/blueprints/layout_blueprints.hpp" #include "utils/blueprints/network_blueprints.hpp" #include "utils/equivalence_checking_utils.hpp" @@ -26,8 +27,6 @@ #include #include -#include - using namespace fiction; TEST_CASE("Simulation", "[mockturtle]") diff --git a/test/algorithms/simulation/sidb/critical_temperature.cpp b/test/algorithms/simulation/sidb/critical_temperature.cpp index a8f96de83..bb0a51610 100644 --- a/test/algorithms/simulation/sidb/critical_temperature.cpp +++ b/test/algorithms/simulation/sidb/critical_temperature.cpp @@ -3,13 +3,16 @@ // #include +#include #include #include #include +#include +#include +#include #include #include -#include #include #include diff --git a/test/algorithms/simulation/sidb/determine_physically_valid_parameters.cpp b/test/algorithms/simulation/sidb/determine_physically_valid_parameters.cpp index 10c797041..78663567f 100644 --- a/test/algorithms/simulation/sidb/determine_physically_valid_parameters.cpp +++ b/test/algorithms/simulation/sidb/determine_physically_valid_parameters.cpp @@ -8,13 +8,12 @@ #include #include +#include #include -#include #include #include #include #include -#include using namespace fiction; @@ -41,19 +40,14 @@ TEST_CASE("Determine physical parameters for CDS of SiQAD Y-shaped AND gate, 10 operational_domain_params op_domain_params{}; op_domain_params.simulation_parameters = sim_params; - op_domain_params.x_dimension = sweep_parameter::EPSILON_R; - op_domain_params.x_min = 4.1; - op_domain_params.x_max = 6.0; - op_domain_params.x_step = 0.1; - op_domain_params.y_dimension = sweep_parameter::LAMBDA_TF; - op_domain_params.y_min = 4.1; - op_domain_params.y_max = 6.0; - op_domain_params.y_step = 0.1; + + op_domain_params.sweep_dimensions = {operational_domain_value_range{sweep_parameter::EPSILON_R, 4.1, 6.0, 0.1}, + operational_domain_value_range{sweep_parameter::LAMBDA_TF, 4.1, 6.0, 0.1}}; SECTION("Using the typical ground state as given CDS") { - op_domain_params.x_step = 0.3; - op_domain_params.y_step = 0.3; + op_domain_params.sweep_dimensions[0].step = 0.3; + op_domain_params.sweep_dimensions[1].step = 0.3; cds.assign_charge_state({-2, -1, 1}, sidb_charge_state::NEGATIVE); cds.assign_charge_state({0, 0, 1}, sidb_charge_state::NEUTRAL); @@ -91,14 +85,26 @@ TEST_CASE("Determine physical parameters for CDS of SiQAD Y-shaped AND gate, 10 const auto valid_parameters = determine_physically_valid_parameters(cds, op_domain_params); CHECK(valid_parameters.operational_values.size() == 98); - CHECK(find_parameter_point_with_tolerance(valid_parameters.operational_values, parameter_point{5.9, 5.5}) - ->second == 1); - CHECK(find_parameter_point_with_tolerance(valid_parameters.operational_values, parameter_point{5.8, 4.4}) - ->second == 0); - CHECK(find_parameter_point_with_tolerance(valid_parameters.operational_values, parameter_point{5.8, 4.4}) - ->second == 0); - CHECK(find_parameter_point_with_tolerance(valid_parameters.operational_values, parameter_point{6.0, 6.0}) - ->second == 1); + + const auto p1 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{5.9, 5.5}}); + REQUIRE(p1.has_value()); + CHECK(p1->second == 1); + + const auto p2 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{5.8, 4.4}}); + REQUIRE(p2.has_value()); + CHECK(p2->second == 0); + + const auto p3 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{5.8, 4.4}}); + REQUIRE(p3.has_value()); + CHECK(p3->second == 0); + + const auto p4 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{6.0, 6.0}}); + REQUIRE(p4.has_value()); + CHECK(p4->second == 1); } } @@ -106,29 +112,86 @@ TEST_CASE( "Determine physical parameters for CDS (default physical parameters) of Bestagon AND gate, 10 input combination", "[determine-physically-valid-parameters], [quality]") { - auto lyt = blueprints::bestagon_and_gate(); + auto bestagon_and = blueprints::bestagon_and_gate(); - lyt.assign_cell_type({36, 1, 0}, sidb_cell_clk_lyt_siqad::cell_type::EMPTY); - lyt.assign_cell_type({0, 0, 0}, sidb_cell_clk_lyt_siqad::cell_type::EMPTY); + bestagon_and.assign_cell_type({36, 1, 0}, sidb_cell_clk_lyt_siqad::cell_type::EMPTY); + bestagon_and.assign_cell_type({0, 0, 0}, sidb_cell_clk_lyt_siqad::cell_type::EMPTY); sidb_simulation_parameters sim_params{}; sim_params.base = 2; - charge_distribution_surface cds{lyt, sim_params}; + charge_distribution_surface cds{bestagon_and, sim_params}; operational_domain_params op_domain_params{}; op_domain_params.simulation_parameters = sim_params; - op_domain_params.x_dimension = sweep_parameter::EPSILON_R; - op_domain_params.x_min = 5.0; - op_domain_params.x_max = 5.9; - op_domain_params.x_step = 0.1; - op_domain_params.y_dimension = sweep_parameter::LAMBDA_TF; - op_domain_params.y_min = 5.0; - op_domain_params.y_max = 5.9; - op_domain_params.y_step = 0.1; - - SECTION("Using the ground state of default physical parameters as given CDS") + + SECTION("Using the ground state of default physical parameters as given CDS, two dimensional sweep") { + op_domain_params.sweep_dimensions = {operational_domain_value_range{sweep_parameter::EPSILON_R, 5.0, 5.9, 0.1}, + operational_domain_value_range{sweep_parameter::LAMBDA_TF, 5.0, 5.9, 0.1}}; + + cds.assign_charge_state({38, 0, 0}, sidb_charge_state::NEGATIVE); + cds.assign_charge_state({2, 1, 0}, sidb_charge_state::NEGATIVE); + + cds.assign_charge_state({6, 2, 0}, sidb_charge_state::NEUTRAL); + cds.assign_charge_state({32, 2, 0}, sidb_charge_state::NEGATIVE); + cds.assign_charge_state({8, 3, 0}, sidb_charge_state::NEGATIVE); + cds.assign_charge_state({30, 3, 0}, sidb_charge_state::NEUTRAL); + + cds.assign_charge_state({12, 4, 0}, sidb_charge_state::NEUTRAL); + cds.assign_charge_state({26, 4, 0}, sidb_charge_state::NEGATIVE); + cds.assign_charge_state({14, 5, 0}, sidb_charge_state::NEGATIVE); + cds.assign_charge_state({24, 5, 0}, sidb_charge_state::NEUTRAL); + + cds.assign_charge_state({19, 8, 0}, sidb_charge_state::NEUTRAL); + cds.assign_charge_state({18, 9, 0}, sidb_charge_state::NEGATIVE); + cds.assign_charge_state({23, 9, 0}, sidb_charge_state::NEGATIVE); + cds.assign_charge_state({18, 11, 1}, sidb_charge_state::NEUTRAL); + + cds.assign_charge_state({19, 13, 0}, sidb_charge_state::NEGATIVE); + cds.assign_charge_state({20, 14, 0}, sidb_charge_state::NEUTRAL); + + cds.assign_charge_state({24, 15, 0}, sidb_charge_state::NEGATIVE); + cds.assign_charge_state({26, 16, 0}, sidb_charge_state::NEUTRAL); + + cds.assign_charge_state({30, 17, 0}, sidb_charge_state::NEGATIVE); + cds.assign_charge_state({32, 18, 0}, sidb_charge_state::NEUTRAL); + + cds.assign_charge_state({36, 19, 0}, sidb_charge_state::NEGATIVE); + + cds.update_after_charge_change(); + + const auto valid_parameters = determine_physically_valid_parameters(cds, op_domain_params); + REQUIRE(valid_parameters.operational_values.size() == 100); + + const auto p1 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{5.6, 5.0}}); + REQUIRE(p1.has_value()); + CHECK(p1->second == 0); + + const auto p2 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{5.0, 5.9}}); + REQUIRE(p2.has_value()); + CHECK(p2->second == 2); + + const auto p3 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{5.4, 5.3}}); + REQUIRE(p3.has_value()); + CHECK(p3->second == 1); + + const auto p4 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{5.8, 5.3}}); + REQUIRE(p4.has_value()); + CHECK(p4->second == 0); + } + + SECTION("Using the ground state of default physical parameters as given CDS, three dimensional sweep") + { + op_domain_params.sweep_dimensions = { + operational_domain_value_range{sweep_parameter::EPSILON_R, 5.5, 5.7, 0.1}, + operational_domain_value_range{sweep_parameter::LAMBDA_TF, 5.0, 5.2, 0.1}, + operational_domain_value_range{sweep_parameter::MU_MINUS, -0.33, -0.31, 0.01}}; + cds.assign_charge_state({38, 0, 0}, sidb_charge_state::NEGATIVE); cds.assign_charge_state({2, 1, 0}, sidb_charge_state::NEGATIVE); @@ -161,14 +224,25 @@ TEST_CASE( cds.update_after_charge_change(); const auto valid_parameters = determine_physically_valid_parameters(cds, op_domain_params); - CHECK(valid_parameters.operational_values.size() == 100); - CHECK(find_parameter_point_with_tolerance(valid_parameters.operational_values, parameter_point{5.6, 5.0}) - ->second == 0); - CHECK(find_parameter_point_with_tolerance(valid_parameters.operational_values, parameter_point{5.0, 5.9}) - ->second == 2); - CHECK(find_parameter_point_with_tolerance(valid_parameters.operational_values, parameter_point{5.4, 5.3}) - ->second == 1); - CHECK(find_parameter_point_with_tolerance(valid_parameters.operational_values, parameter_point{5.8, 5.3}) - ->second == 0); + REQUIRE(valid_parameters.operational_values.size() == 27); + const auto p1 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{5.6, 5.0, -0.32}}); + REQUIRE(p1.has_value()); + CHECK(p1->second == 0); + + const auto p2 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{5.6, 5.0, -0.33}}); + REQUIRE(p2.has_value()); + CHECK(p2->second == 0); + + const auto p3 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{5.6, 5.0, -0.31}}); + REQUIRE(p3.has_value()); + CHECK(p3->second == 1); + + const auto p4 = + detail::contains_parameter_point(valid_parameters.operational_values, parameter_point{{5.7, 5.2, -0.33}}); + REQUIRE(p4.has_value()); + CHECK(p4->second == 0); } } diff --git a/test/algorithms/simulation/sidb/operational_domain.cpp b/test/algorithms/simulation/sidb/operational_domain.cpp index f5732a372..f1bd44ade 100644 --- a/test/algorithms/simulation/sidb/operational_domain.cpp +++ b/test/algorithms/simulation/sidb/operational_domain.cpp @@ -11,50 +11,242 @@ #include #include #include -#include #include -#include #include #include #include +#include #include using namespace fiction; -TEST_CASE("Structured binding support for parameter_points", "[operational-domain]") +void check_op_domain_params_and_operational_status( + const operational_domain& op_domain, const operational_domain_params& params, + const std::optional& status) noexcept { - auto param_point = parameter_point{1.0, 2.0}; + REQUIRE(params.sweep_dimensions.size() == op_domain.dimensions.size()); - CHECK(param_point.x == 1.0); - CHECK(param_point.y == 2.0); + for (auto d = 0u; d < params.sweep_dimensions.size(); ++d) + { + CHECK(op_domain.dimensions[d] == params.sweep_dimensions[d].dimension); + } - const auto& [x, y] = param_point; + for (const auto& [coord, op_value] : op_domain.operational_values) + { + for (auto d = 0u; d < params.sweep_dimensions.size(); ++d) + { + const auto& sweep_param = params.sweep_dimensions[d]; + const auto& coord_value = coord.parameters[d]; - CHECK(x == 1.0); - CHECK(y == 2.0); + CHECK(sweep_param.min <= coord_value); + CHECK(sweep_param.max >= coord_value); + CHECK(sweep_param.step > 0.0); + } + + if (status.has_value()) + { + if (status.value() == operational_status::OPERATIONAL) + { + CHECK(op_value == *status); + } + } + } } -void check_op_domain_params_and_operational_status(const operational_domain<>& op_domain, - const operational_domain_params& params, - const std::optional& status) noexcept +TEST_CASE("Test parameter point", "[operational-domain]") { - CHECK(op_domain.x_dimension == params.x_dimension); - CHECK(op_domain.y_dimension == params.y_dimension); + // Test default constructor + const parameter_point p_default; + REQUIRE(p_default.parameters.empty()); - for (const auto& [coord, op_value] : op_domain.operational_values) + // Test parameterized constructor + const std::vector values = {1.0, 2.0, 3.0}; + const parameter_point p_param(values); + REQUIRE(p_param.parameters == values); + + // Test equality operator + const parameter_point p1({1.0, 2.0, 3.0}); + const parameter_point p2({1.0, 2.0, 3.0}); + const parameter_point p3({1.0, 2.0, 3.0000001}); + + SECTION("Equality operator - exact equality") { - CHECK(coord.x - params.x_min > -physical_constants::POP_STABILITY_ERR); - CHECK(params.x_max - coord.x > -physical_constants::POP_STABILITY_ERR); - CHECK(coord.y - params.y_min > -physical_constants::POP_STABILITY_ERR); - CHECK(params.y_max - coord.y > -physical_constants::POP_STABILITY_ERR); + REQUIRE(p1 == p2); + } - if (status.has_value()) + SECTION("Equality operator - within tolerance") + { + REQUIRE(p1 == p3); + } + + // Test inequality operator + const parameter_point p4({1.0, 2.0, 3.1}); + REQUIRE(p1 != p4); + + // Test structured bindings (get() method) + SECTION("Structured bindings - valid index") + { + REQUIRE(p1.get<0>() == 1.0); + REQUIRE(p1.get<1>() == 2.0); + REQUIRE(p1.get<2>() == 3.0); + } + + SECTION("Structured bindings - invalid index") + { + REQUIRE_THROWS_AS(p1.get<3>(), std::out_of_range); + } +} + +TEST_CASE("Error handling of operational domain algorithms", "[operational-domain]") +{ + const sidb_100_cell_clk_lyt_siqad lat{sidb_cell_clk_lyt_siqad{}}; // empty layout + + SECTION("invalid number of dimensions") + { + operational_domain_params zero_dimensional_params{}; + operational_domain_params one_dimensional_params{}; + operational_domain_params three_dimensional_params{}; + operational_domain_params four_dimensional_params{}; + + // 0-dimensional + zero_dimensional_params.sweep_dimensions = {}; + + // 1-dimensional + one_dimensional_params.sweep_dimensions = {{sweep_parameter::EPSILON_R}}; + + // 3-dimensional + three_dimensional_params.sweep_dimensions = {{sweep_parameter::EPSILON_R}, + {sweep_parameter::LAMBDA_TF}, + {sweep_parameter::MU_MINUS}}; + + // 4-dimensional + four_dimensional_params.sweep_dimensions = {{sweep_parameter::EPSILON_R}, + {sweep_parameter::LAMBDA_TF}, + {sweep_parameter::MU_MINUS}, + {sweep_parameter::EPSILON_R}}; + + SECTION("flood_fill") { - if (status.value() == operational_status::OPERATIONAL) + // flood fill operates on 2-dimensional and 3-dimensional parameter spaces + for (const auto& params : {zero_dimensional_params, one_dimensional_params, four_dimensional_params}) { - CHECK(op_value == *status); + CHECK_THROWS_AS(operational_domain_flood_fill(lat, std::vector{create_id_tt()}, 1, params), + std::invalid_argument); + } + } + SECTION("contour_tracing") + { + // contour tracing operates only on 2-dimensional parameter spaces + for (const auto& params : + {zero_dimensional_params, one_dimensional_params, three_dimensional_params, four_dimensional_params}) + { + CHECK_THROWS_AS(operational_domain_contour_tracing(lat, std::vector{create_id_tt()}, 1, params), + std::invalid_argument); + } + } + } + + SECTION("invalid sweep dimensions") + { + operational_domain_params invalid_params_1{}; + operational_domain_params invalid_params_2{}; + operational_domain_params invalid_params_3{}; + + SECTION("min/max mismatch") + { + // 1-dimensional with invalid min/max on 1st dimension + invalid_params_1.sweep_dimensions = {{sweep_parameter::EPSILON_R}}; + invalid_params_1.sweep_dimensions[0].min = 10.0; + invalid_params_1.sweep_dimensions[0].max = 1.0; + invalid_params_1.sweep_dimensions[0].step = 0.1; + + // 2-dimensional with invalid min/max on 2nd dimension + invalid_params_2.sweep_dimensions = {{sweep_parameter::EPSILON_R}, {sweep_parameter::LAMBDA_TF}}; + invalid_params_2.sweep_dimensions[1].min = 5.5; + invalid_params_2.sweep_dimensions[1].max = 5.4; + invalid_params_2.sweep_dimensions[1].step = 0.1; + + // 3-dimensional with invalid min/max on 3rd dimension + invalid_params_3.sweep_dimensions = {{sweep_parameter::EPSILON_R}, + {sweep_parameter::LAMBDA_TF}, + {sweep_parameter::MU_MINUS}}; + invalid_params_3.sweep_dimensions[2].min = -0.4; + invalid_params_3.sweep_dimensions[2].max = -0.5; + invalid_params_3.sweep_dimensions[2].step = 0.01; + + for (const auto& params : {invalid_params_1, invalid_params_2, invalid_params_3}) + { + SECTION("grid_search") + { + CHECK_THROWS_AS(operational_domain_grid_search(lat, std::vector{create_id_tt()}, params), + std::invalid_argument); + } + SECTION("random_sampling") + { + CHECK_THROWS_AS( + operational_domain_random_sampling(lat, std::vector{create_id_tt()}, 100, params), + std::invalid_argument); + } + SECTION("flood_fill") + { + CHECK_THROWS_AS(operational_domain_flood_fill(lat, std::vector{create_id_tt()}, 1, params), + std::invalid_argument); + } + SECTION("contour_tracing") + { + CHECK_THROWS_AS(operational_domain_contour_tracing(lat, std::vector{create_id_tt()}, 1, params), + std::invalid_argument); + } + } + } + + SECTION("negative step size") + { + // 1-dimensional with negative step size on 1st dimension + invalid_params_1.sweep_dimensions = {{sweep_parameter::EPSILON_R}}; + invalid_params_1.sweep_dimensions[0].min = 1.0; + invalid_params_1.sweep_dimensions[0].max = 10.0; + invalid_params_1.sweep_dimensions[0].step = -0.5; + + // 2-dimensional with negative step size on 2nd dimension + invalid_params_2.sweep_dimensions = {{sweep_parameter::EPSILON_R}, {sweep_parameter::LAMBDA_TF}}; + invalid_params_2.sweep_dimensions[1].min = 5.5; + invalid_params_2.sweep_dimensions[1].max = 5.6; + invalid_params_2.sweep_dimensions[1].step = -0.1; + + // 3-dimensional with negative step size on 3rd dimension + invalid_params_3.sweep_dimensions = {{sweep_parameter::EPSILON_R}, + {sweep_parameter::LAMBDA_TF}, + {sweep_parameter::MU_MINUS}}; + invalid_params_3.sweep_dimensions[2].min = -0.4; + invalid_params_3.sweep_dimensions[2].max = -0.5; + invalid_params_3.sweep_dimensions[2].step = -0.01; + + for (const auto& params : {invalid_params_1, invalid_params_2, invalid_params_3}) + { + SECTION("grid_search") + { + CHECK_THROWS_AS(operational_domain_grid_search(lat, std::vector{create_id_tt()}, params), + std::invalid_argument); + } + SECTION("random_sampling") + { + CHECK_THROWS_AS( + operational_domain_random_sampling(lat, std::vector{create_id_tt()}, 100, params), + std::invalid_argument); + } + SECTION("flood_fill") + { + CHECK_THROWS_AS(operational_domain_flood_fill(lat, std::vector{create_id_tt()}, 1, params), + std::invalid_argument); + } + SECTION("contour_tracing") + { + CHECK_THROWS_AS(operational_domain_contour_tracing(lat, std::vector{create_id_tt()}, 1, params), + std::invalid_argument); + } } } } @@ -88,20 +280,21 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") operational_domain_params op_domain_params{}; op_domain_params.simulation_parameters = sim_params; - op_domain_params.x_dimension = sweep_parameter::EPSILON_R; - op_domain_params.y_dimension = sweep_parameter::LAMBDA_TF; + op_domain_params.sweep_dimensions = {{sweep_parameter::EPSILON_R}, {sweep_parameter::LAMBDA_TF}}; operational_domain_stats op_domain_stats{}; SECTION("operational area, only one parameter point") { - op_domain_params.x_min = 5.5; - op_domain_params.x_max = 5.5; - op_domain_params.x_step = 0.1; + // set x-dimension + op_domain_params.sweep_dimensions[0].min = 5.5; + op_domain_params.sweep_dimensions[0].max = 5.5; + op_domain_params.sweep_dimensions[0].step = 0.1; - op_domain_params.y_min = 5.0; - op_domain_params.y_max = 5.0; - op_domain_params.y_step = 0.1; + // set y-dimension + op_domain_params.sweep_dimensions[1].min = 5.0; + op_domain_params.sweep_dimensions[1].max = 5.0; + op_domain_params.sweep_dimensions[1].step = 0.1; SECTION("grid_search") { @@ -119,6 +312,29 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") CHECK(op_domain_stats.num_evaluated_parameter_combinations == 1); CHECK(op_domain_stats.num_operational_parameter_combinations == 1); CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); + + SECTION("3-dimensional") + { + const auto z_dimension = operational_domain_value_range{sweep_parameter::MU_MINUS, -0.32, -0.32, 0.01}; + + op_domain_params.sweep_dimensions.push_back(z_dimension); + + const auto op_domain_3d = operational_domain_grid_search(lat, std::vector{create_id_tt()}, + op_domain_params, &op_domain_stats); + + // check if the operational domain has the correct size + CHECK(op_domain_3d.operational_values.size() == 1); + + // for the selected range, all samples should be within the parameters and operational + check_op_domain_params_and_operational_status(op_domain_3d, op_domain_params, + operational_status::OPERATIONAL); + + CHECK(mockturtle::to_seconds(op_domain_stats.time_total) > 0.0); + CHECK(op_domain_stats.num_simulator_invocations == 2); + CHECK(op_domain_stats.num_evaluated_parameter_combinations == 1); + CHECK(op_domain_stats.num_operational_parameter_combinations == 1); + CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); + } } SECTION("random_sampling") { @@ -137,6 +353,30 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") CHECK(op_domain_stats.num_evaluated_parameter_combinations > 0); CHECK(op_domain_stats.num_operational_parameter_combinations <= 100); CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); + + SECTION("3-dimensional") + { + const auto z_dimension = operational_domain_value_range{sweep_parameter::MU_MINUS, -0.32, -0.32, 0.01}; + + op_domain_params.sweep_dimensions.push_back(z_dimension); + + const auto op_domain_3d = operational_domain_random_sampling(lat, std::vector{create_id_tt()}, 100, + op_domain_params, &op_domain_stats); + + // check if the operational domain has the correct size + CHECK(op_domain_3d.operational_values.size() == 1); + + // for the selected range, all samples should be within the parameters and operational + check_op_domain_params_and_operational_status(op_domain_3d, op_domain_params, + operational_status::OPERATIONAL); + + CHECK(mockturtle::to_seconds(op_domain_stats.time_total) > 0.0); + CHECK(op_domain_stats.num_simulator_invocations <= 200); + CHECK(op_domain_stats.num_evaluated_parameter_combinations <= 100); + CHECK(op_domain_stats.num_evaluated_parameter_combinations > 0); + CHECK(op_domain_stats.num_operational_parameter_combinations <= 100); + CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); + } } SECTION("flood_fill") { @@ -154,6 +394,29 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") CHECK(op_domain_stats.num_evaluated_parameter_combinations == 1); CHECK(op_domain_stats.num_operational_parameter_combinations == 1); CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); + + SECTION("3-dimensional") + { + const auto z_dimension = operational_domain_value_range{sweep_parameter::MU_MINUS, -0.32, -0.32, 0.01}; + + op_domain_params.sweep_dimensions.push_back(z_dimension); + + const auto op_domain_3d = operational_domain_flood_fill(lat, std::vector{create_id_tt()}, 1, + op_domain_params, &op_domain_stats); + + // check if the operational domain has the correct size + CHECK(op_domain_3d.operational_values.size() == 1); + + // for the selected range, all samples should be within the parameters and operational + check_op_domain_params_and_operational_status(op_domain_3d, op_domain_params, + operational_status::OPERATIONAL); + + CHECK(mockturtle::to_seconds(op_domain_stats.time_total) > 0.0); + CHECK(op_domain_stats.num_simulator_invocations == 2); + CHECK(op_domain_stats.num_evaluated_parameter_combinations == 1); + CHECK(op_domain_stats.num_operational_parameter_combinations == 1); + CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); + } } SECTION("contour_tracing") { @@ -176,13 +439,15 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") SECTION("operational area, same number of steps in x- and y-direction") { - op_domain_params.x_min = 5.1; - op_domain_params.x_max = 6.0; - op_domain_params.x_step = 0.1; + // set x-dimension + op_domain_params.sweep_dimensions[0].min = 5.1; + op_domain_params.sweep_dimensions[0].max = 6.0; + op_domain_params.sweep_dimensions[0].step = 0.1; - op_domain_params.y_min = 4.5; - op_domain_params.y_max = 5.4; - op_domain_params.y_step = 0.1; + // set y-dimension + op_domain_params.sweep_dimensions[1].min = 4.5; + op_domain_params.sweep_dimensions[1].max = 5.4; + op_domain_params.sweep_dimensions[1].step = 0.1; SECTION("grid_search") { @@ -257,20 +522,22 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") SECTION("operational area, different number of steps in x- and y-direction") { - op_domain_params.x_min = 5.1; - op_domain_params.x_max = 6.0; - op_domain_params.x_step = 0.1; + // set x-dimension + op_domain_params.sweep_dimensions[0].min = 5.1; + op_domain_params.sweep_dimensions[0].max = 6.0; + op_domain_params.sweep_dimensions[0].step = 0.1; - op_domain_params.y_min = 4.5; - op_domain_params.y_max = 4.9; - op_domain_params.y_step = 0.1; + // set y-dimension + op_domain_params.sweep_dimensions[1].min = 4.5; + op_domain_params.sweep_dimensions[1].max = 4.9; + op_domain_params.sweep_dimensions[1].step = 0.1; SECTION("grid_search") { const auto op_domain = operational_domain_grid_search(lat, std::vector{create_id_tt()}, op_domain_params, &op_domain_stats); - // check if the operational domain has the correct size (10 steps in each dimension) + // check if the operational domain has the correct size CHECK(op_domain.operational_values.size() == 50); // for the selected range, all samples should be within the parameters and operational @@ -281,6 +548,29 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") CHECK(op_domain_stats.num_evaluated_parameter_combinations == 50); CHECK(op_domain_stats.num_operational_parameter_combinations == 50); CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); + + SECTION("3-dimensional") + { + const auto z_dimension = operational_domain_value_range{sweep_parameter::MU_MINUS, -0.35, -0.29, 0.01}; + + op_domain_params.sweep_dimensions.push_back(z_dimension); + + const auto op_domain_3d = operational_domain_grid_search(lat, std::vector{create_id_tt()}, + op_domain_params, &op_domain_stats); + + // check if the operational domain has the correct size + CHECK(op_domain_3d.operational_values.size() == 350); + + // for the selected range, all samples should be within the parameters and operational + check_op_domain_params_and_operational_status(op_domain_3d, op_domain_params, + operational_status::OPERATIONAL); + + CHECK(mockturtle::to_seconds(op_domain_stats.time_total) > 0.0); + CHECK(op_domain_stats.num_simulator_invocations == 700); + CHECK(op_domain_stats.num_evaluated_parameter_combinations == 350); + CHECK(op_domain_stats.num_operational_parameter_combinations == 350); + CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); + } } SECTION("random_sampling") { @@ -299,8 +589,31 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") CHECK(op_domain_stats.num_evaluated_parameter_combinations > 0); CHECK(op_domain_stats.num_operational_parameter_combinations <= 100); CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); - } + SECTION("3-dimensional") + { + const auto z_dimension = operational_domain_value_range{sweep_parameter::MU_MINUS, -0.35, -0.29, 0.01}; + + op_domain_params.sweep_dimensions.push_back(z_dimension); + + const auto op_domain_3d = operational_domain_random_sampling(lat, std::vector{create_id_tt()}, 100, + op_domain_params, &op_domain_stats); + + // check if the operational domain has the correct size + CHECK(op_domain_3d.operational_values.size() <= 350); + + // for the selected range, all samples should be within the parameters and operational + check_op_domain_params_and_operational_status(op_domain_3d, op_domain_params, + operational_status::OPERATIONAL); + + CHECK(mockturtle::to_seconds(op_domain_stats.time_total) > 0.0); + CHECK(op_domain_stats.num_simulator_invocations <= 700); + CHECK(op_domain_stats.num_evaluated_parameter_combinations <= 350); + CHECK(op_domain_stats.num_evaluated_parameter_combinations > 0); + CHECK(op_domain_stats.num_operational_parameter_combinations <= 350); + CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); + } + } SECTION("flood_fill") { const auto op_domain = operational_domain_flood_fill(lat, std::vector{create_id_tt()}, 1, @@ -317,6 +630,29 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") CHECK(op_domain_stats.num_evaluated_parameter_combinations == 50); CHECK(op_domain_stats.num_operational_parameter_combinations == 50); CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); + + SECTION("3-dimensional") + { + const auto z_dimension = operational_domain_value_range{sweep_parameter::MU_MINUS, -0.35, -0.29, 0.01}; + + op_domain_params.sweep_dimensions.push_back(z_dimension); + + const auto op_domain_3d = operational_domain_flood_fill(lat, std::vector{create_id_tt()}, 100, + op_domain_params, &op_domain_stats); + + // check if the operational domain has the correct size + CHECK(op_domain_3d.operational_values.size() == 350); + + // for the selected range, all samples should be within the parameters and operational + check_op_domain_params_and_operational_status(op_domain_3d, op_domain_params, + operational_status::OPERATIONAL); + + CHECK(mockturtle::to_seconds(op_domain_stats.time_total) > 0.0); + CHECK(op_domain_stats.num_simulator_invocations == 700); + CHECK(op_domain_stats.num_evaluated_parameter_combinations == 350); + CHECK(op_domain_stats.num_operational_parameter_combinations == 350); + CHECK(op_domain_stats.num_non_operational_parameter_combinations == 0); + } } SECTION("contour_tracing") { @@ -339,13 +675,15 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") SECTION("non-operational area") { - op_domain_params.x_min = 2.5; - op_domain_params.x_max = 3.4; - op_domain_params.x_step = 0.1; + // set x-dimension + op_domain_params.sweep_dimensions[0].min = 2.5; + op_domain_params.sweep_dimensions[0].max = 3.4; + op_domain_params.sweep_dimensions[0].step = 0.1; - op_domain_params.y_min = 4.5; - op_domain_params.y_max = 5.4; - op_domain_params.y_step = 0.1; + // set y-dimension + op_domain_params.sweep_dimensions[1].min = 4.5; + op_domain_params.sweep_dimensions[1].max = 5.4; + op_domain_params.sweep_dimensions[1].step = 0.1; SECTION("grid_search") { @@ -364,6 +702,29 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") CHECK(op_domain_stats.num_evaluated_parameter_combinations == 100); CHECK(op_domain_stats.num_operational_parameter_combinations == 0); CHECK(op_domain_stats.num_non_operational_parameter_combinations == 100); + + SECTION("3-dimensional") + { + const auto z_dimension = operational_domain_value_range{sweep_parameter::MU_MINUS, -0.14, -0.10, 0.01}; + + op_domain_params.sweep_dimensions.push_back(z_dimension); + + const auto op_domain_3d = operational_domain_grid_search(lat, std::vector{create_id_tt()}, + op_domain_params, &op_domain_stats); + + // check if the operational domain has the correct size + CHECK(op_domain_3d.operational_values.size() == 500); + + // for the selected range, all samples should be within the parameters and non-operational + check_op_domain_params_and_operational_status(op_domain_3d, op_domain_params, + operational_status::NON_OPERATIONAL); + + CHECK(mockturtle::to_seconds(op_domain_stats.time_total) > 0.0); + CHECK(op_domain_stats.num_simulator_invocations <= 1000); + CHECK(op_domain_stats.num_evaluated_parameter_combinations == 500); + CHECK(op_domain_stats.num_operational_parameter_combinations == 0); + CHECK(op_domain_stats.num_non_operational_parameter_combinations == 500); + } } SECTION("random_sampling") { @@ -382,6 +743,29 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") CHECK(op_domain_stats.num_evaluated_parameter_combinations <= 5000); CHECK(op_domain_stats.num_operational_parameter_combinations == 0); CHECK(op_domain_stats.num_non_operational_parameter_combinations <= 5000); + + SECTION("3-dimensional") + { + const auto z_dimension = operational_domain_value_range{sweep_parameter::MU_MINUS, -0.14, -0.10, 0.01}; + + op_domain_params.sweep_dimensions.push_back(z_dimension); + + const auto op_domain_3d = operational_domain_random_sampling(lat, std::vector{create_id_tt()}, 5000, + op_domain_params, &op_domain_stats); + + // check if the operational domain has the correct maximum size + CHECK(op_domain_3d.operational_values.size() <= 5000); + + // for the selected range, all samples should be within the parameters and non-operational + check_op_domain_params_and_operational_status(op_domain_3d, op_domain_params, + operational_status::NON_OPERATIONAL); + + CHECK(mockturtle::to_seconds(op_domain_stats.time_total) > 0.0); + CHECK(op_domain_stats.num_simulator_invocations < 10000); + CHECK(op_domain_stats.num_evaluated_parameter_combinations <= 5000); + CHECK(op_domain_stats.num_operational_parameter_combinations == 0); + CHECK(op_domain_stats.num_non_operational_parameter_combinations <= 5000); + } } SECTION("flood_fill") { @@ -400,6 +784,29 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") CHECK(op_domain_stats.num_evaluated_parameter_combinations <= 100); CHECK(op_domain_stats.num_operational_parameter_combinations == 0); CHECK(op_domain_stats.num_non_operational_parameter_combinations <= 100); + + SECTION("3-dimensional") + { + const auto z_dimension = operational_domain_value_range{sweep_parameter::MU_MINUS, -0.14, -0.10, 0.01}; + + op_domain_params.sweep_dimensions.push_back(z_dimension); + + const auto op_domain_3d = operational_domain_flood_fill(lat, std::vector{create_id_tt()}, 25, + op_domain_params, &op_domain_stats); + + // check if the operational domain has the correct maximum size + CHECK(op_domain_3d.operational_values.size() <= 500); + + // for the selected range, all samples should be within the parameters and non-operational + check_op_domain_params_and_operational_status(op_domain_3d, op_domain_params, + operational_status::NON_OPERATIONAL); + + CHECK(mockturtle::to_seconds(op_domain_stats.time_total) > 0.0); + CHECK(op_domain_stats.num_simulator_invocations <= 1000); + CHECK(op_domain_stats.num_evaluated_parameter_combinations <= 500); + CHECK(op_domain_stats.num_operational_parameter_combinations == 0); + CHECK(op_domain_stats.num_non_operational_parameter_combinations <= 500); + } } SECTION("contour_tracing") { @@ -422,13 +829,15 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") } SECTION("floating-point error") { - op_domain_params.x_min = 2.5; - op_domain_params.x_max = 4.4; - op_domain_params.x_step = 0.9; + // set x-dimension + op_domain_params.sweep_dimensions[0].min = 2.5; + op_domain_params.sweep_dimensions[0].max = 4.4; + op_domain_params.sweep_dimensions[0].step = 0.9; - op_domain_params.y_min = 2.5; - op_domain_params.y_max = 2.5; - op_domain_params.y_step = 0.1; + // set y-dimension + op_domain_params.sweep_dimensions[1].min = 2.5; + op_domain_params.sweep_dimensions[1].max = 2.5; + op_domain_params.sweep_dimensions[1].step = 0.1; SECTION("flood_fill") { @@ -444,13 +853,15 @@ TEST_CASE("BDL wire operational domain computation", "[operational-domain]") } SECTION("semi-operational area") { - op_domain_params.x_min = 0.5; - op_domain_params.x_max = 4.25; - op_domain_params.x_step = 0.25; + // set x-dimension + op_domain_params.sweep_dimensions[0].min = 0.5; + op_domain_params.sweep_dimensions[0].max = 4.25; + op_domain_params.sweep_dimensions[0].step = 0.25; - op_domain_params.y_min = 0.5; - op_domain_params.y_max = 4.25; - op_domain_params.y_step = 0.25; + // set y-dimension + op_domain_params.sweep_dimensions[1].min = 0.5; + op_domain_params.sweep_dimensions[1].max = 4.25; + op_domain_params.sweep_dimensions[1].step = 0.25; SECTION("grid_search") { @@ -554,14 +965,8 @@ TEST_CASE("SiQAD's AND gate operational domain computation", "[operational-domai operational_domain_params op_domain_params{}; op_domain_params.simulation_parameters = sim_params; - op_domain_params.x_dimension = ::sweep_parameter::EPSILON_R; - op_domain_params.x_min = 5.1; - op_domain_params.x_max = 6.0; - op_domain_params.x_step = 0.1; - op_domain_params.y_dimension = sweep_parameter::LAMBDA_TF; - op_domain_params.y_min = 4.5; - op_domain_params.y_max = 5.4; - op_domain_params.y_step = 0.1; + op_domain_params.sweep_dimensions = {{sweep_parameter::EPSILON_R, 5.1, 6.0, 0.1}, + {sweep_parameter::LAMBDA_TF, 4.5, 5.4, 0.1}}; operational_domain_stats op_domain_stats{}; @@ -677,14 +1082,8 @@ TEST_CASE("SiQAD's AND gate operational domain computation, using cube coordinat operational_domain_params op_domain_params{}; op_domain_params.simulation_parameters = sim_params; - op_domain_params.x_dimension = sweep_parameter::EPSILON_R; - op_domain_params.x_min = 5.1; - op_domain_params.x_max = 6.0; - op_domain_params.x_step = 0.1; - op_domain_params.y_dimension = sweep_parameter::LAMBDA_TF; - op_domain_params.y_min = 4.5; - op_domain_params.y_max = 5.4; - op_domain_params.y_step = 0.1; + op_domain_params.sweep_dimensions = {{sweep_parameter::EPSILON_R, 5.1, 6.0, 0.1}, + {sweep_parameter::LAMBDA_TF, 4.5, 5.4, 0.1}}; operational_domain_stats op_domain_stats{}; @@ -769,14 +1168,8 @@ TEMPLATE_TEST_CASE("AND gate on the H-Si(111)-1x1 surface", "[operational-domain operational_domain_params op_domain_params{}; op_domain_params.simulation_parameters = sim_params; - op_domain_params.x_dimension = sweep_parameter::EPSILON_R; - op_domain_params.x_min = 5.60; - op_domain_params.x_max = 5.61; - op_domain_params.x_step = 0.01; - op_domain_params.y_dimension = sweep_parameter::LAMBDA_TF; - op_domain_params.y_min = 5.0; - op_domain_params.y_max = 5.01; - op_domain_params.y_step = 0.01; + op_domain_params.sweep_dimensions = {{sweep_parameter::EPSILON_R, 5.60, 5.61, 0.01}, + {sweep_parameter::LAMBDA_TF, 5.0, 5.01, 0.01}}; operational_domain_stats op_domain_stats{}; diff --git a/test/io/write_operational_domain.cpp b/test/io/write_operational_domain.cpp index 07677d68d..30215d413 100644 --- a/test/io/write_operational_domain.cpp +++ b/test/io/write_operational_domain.cpp @@ -15,12 +15,15 @@ using namespace fiction; TEST_CASE("Write empty operational domain", "[write-operational-domain]") { - operational_domain opdom{}; - std::ostringstream os{}; SECTION("default sweep dimensions") { + operational_domain opdom{}; + + opdom.dimensions.push_back(sweep_parameter::EPSILON_R); + opdom.dimensions.push_back(sweep_parameter::LAMBDA_TF); + static constexpr const char* expected = "epsilon_r,lambda_tf,operational status\n"; write_operational_domain(opdom, os); @@ -29,8 +32,10 @@ TEST_CASE("Write empty operational domain", "[write-operational-domain]") } SECTION("custom sweep dimensions") { - opdom.x_dimension = sweep_parameter::LAMBDA_TF; - opdom.y_dimension = sweep_parameter::MU_MINUS; + operational_domain opdom{}; + + opdom.dimensions.push_back(sweep_parameter::LAMBDA_TF); + opdom.dimensions.push_back(sweep_parameter::MU_MINUS); static constexpr const char* expected = "lambda_tf,mu_minus,operational status\n"; @@ -43,15 +48,18 @@ TEST_CASE("Write empty operational domain", "[write-operational-domain]") TEST_CASE("Write simple operational domain", "[write-operational-domain]") { operational_domain opdom{}; - opdom.operational_values = {{{0, 0}, operational_status::OPERATIONAL}, - {{0, 1}, operational_status::NON_OPERATIONAL}}; + + opdom.dimensions.push_back(sweep_parameter::EPSILON_R); + opdom.dimensions.push_back(sweep_parameter::LAMBDA_TF); + + opdom.operational_values = {{parameter_point{{0, 0}}, operational_status::OPERATIONAL}, + {parameter_point{{0, 1}}, operational_status::NON_OPERATIONAL}}; std::ostringstream os{}; SECTION("default operational tags") { - std::set expected{"epsilon_r,lambda_tf,operational status", "0,0,operational", - "0,1,non-operational"}; + std::set expected{"epsilon_r,lambda_tf,operational status", "0,0,1", "0,1,0"}; write_operational_domain(opdom, os); const auto os_str = os.str(); @@ -80,22 +88,44 @@ TEST_CASE("Write simple operational domain", "[write-operational-domain]") CHECK(expected.find(line) != expected.end()); } } + + SECTION("skip non-operational samples") + { + write_operational_domain_params params{}; + params.writing_mode = write_operational_domain_params::sample_writing_mode::OPERATIONAL_ONLY; + + std::set expected{"epsilon_r,lambda_tf,operational status", "0,0,1"}; + + write_operational_domain(opdom, os, params); + const auto os_str = os.str(); + + std::istringstream is{os_str}; + + for (std::string line{}; std::getline(is, line);) + { + CHECK(expected.find(line) != expected.end()); + } + } } TEST_CASE("Write operational domain with floating-point parameter values", "[write-operational-domain]") { operational_domain opdom{}; - opdom.operational_values = {{{0.1, 0.2}, operational_status::OPERATIONAL}, - {{0.3, 0.4}, operational_status::NON_OPERATIONAL}, - {{1.2, 1.4}, operational_status::OPERATIONAL}, - {{2.4, 5.75}, operational_status::NON_OPERATIONAL}}; + + opdom.dimensions.push_back(sweep_parameter::EPSILON_R); + opdom.dimensions.push_back(sweep_parameter::LAMBDA_TF); + + opdom.operational_values = {{parameter_point{{0.1, 0.2}}, operational_status::OPERATIONAL}, + {parameter_point{{0.3, 0.4}}, operational_status::NON_OPERATIONAL}, + {parameter_point{{1.2, 1.4}}, operational_status::OPERATIONAL}, + {parameter_point{{2.4, 5.75}}, operational_status::NON_OPERATIONAL}}; std::ostringstream os{}; SECTION("default operational tags") { - std::set expected{"epsilon_r,lambda_tf,operational status", "0.1,0.2,operational", - "0.3,0.4,non-operational", "1.2,1.4,operational", "2.4,5.75,non-operational"}; + std::set expected{"epsilon_r,lambda_tf,operational status", "0.1,0.2,1", "0.3,0.4,0", "1.2,1.4,1", + "2.4,5.75,0"}; write_operational_domain(opdom, os); const auto os_str = os.str(); @@ -109,10 +139,28 @@ TEST_CASE("Write operational domain with floating-point parameter values", "[wri } SECTION("custom operational tags") { - const write_operational_domain_params params = {"1", "0"}; + const write_operational_domain_params params = {"operational", "non-operational"}; - std::set expected{"epsilon_r,lambda_tf,operational status", "0.1,0.2,1", "0.3,0.4,0", "1.2,1.4,1", - "2.4,5.75,0"}; + std::set expected{"epsilon_r,lambda_tf,operational status", "0.1,0.2,operational", + "0.3,0.4,non-operational", "1.2,1.4,operational", "2.4,5.75,non-operational"}; + + write_operational_domain(opdom, os, params); + const auto os_str = os.str(); + + std::istringstream is{os_str}; + + for (std::string line{}; std::getline(is, line);) + { + CHECK(expected.find(line) != expected.end()); + } + } + + SECTION("skip non-operational samples") + { + write_operational_domain_params params{}; + params.writing_mode = write_operational_domain_params::sample_writing_mode::OPERATIONAL_ONLY; + + std::set expected{"epsilon_r,lambda_tf,operational status", "0.1,0.2,1", "1.2,1.4,1"}; write_operational_domain(opdom, os, params); const auto os_str = os.str(); diff --git a/test/utils/math_utils.cpp b/test/utils/math_utils.cpp index b08dcf00e..c52517c0e 100644 --- a/test/utils/math_utils.cpp +++ b/test/utils/math_utils.cpp @@ -6,6 +6,9 @@ #include +#include +#include + using namespace fiction; TEST_CASE("round_to_n_decimal_places should round an input number to n decimal places", "[round_to_n_decimal_places]") @@ -112,3 +115,149 @@ TEST_CASE("Binomial Coefficient Tests") REQUIRE(result == 126410606437752); // C(50, 25) = 126,410,606,437,752 } } + +TEST_CASE("Test the computation of the cartesian combinations", "[cartesian_combinations]") +{ + SECTION("Single dimension") + { + const std::vector> input{{1, 2, 3}}; + const std::vector> expected{{1}, {2}, {3}}; + + auto result = cartesian_combinations(input); + + REQUIRE(result == expected); + } + + SECTION("Two dimensions") + { + const std::vector> input{{1, 2}, {3, 4}}; + const std::vector> expected{{1, 3}, {1, 4}, {2, 3}, {2, 4}}; + + auto result = cartesian_combinations(input); + + REQUIRE(result == expected); + } + + SECTION("Three dimensions") + { + const std::vector> input{{1, 2}, {3, 4}, {5, 6}}; + const std::vector> expected{{1, 3, 5}, {1, 3, 6}, {1, 4, 5}, {1, 4, 6}, + {2, 3, 5}, {2, 3, 6}, {2, 4, 5}, {2, 4, 6}}; + + auto result = cartesian_combinations(input); + + REQUIRE(result == expected); + } + + SECTION("Empty input") + { + const std::vector> input{}; + const std::vector> expected{{}}; + + auto result = cartesian_combinations(input); + + REQUIRE(result == expected); + } + + SECTION("Empty dimension") + { + const std::vector> input{{1, 2}, {}}; + const std::vector> expected{}; + + auto result = cartesian_combinations(input); + + REQUIRE(result == expected); + } + + SECTION("Mixed types") + { + const std::vector> input{{"a", "b"}, {"x", "y"}}; + const std::vector> expected{{"a", "x"}, {"a", "y"}, {"b", "x"}, {"b", "y"}}; + + auto result = cartesian_combinations(input); + + REQUIRE(result == expected); + } +} + +TEST_CASE("Test the determination of all combinations of distributing k entities on n positions", + "[determine_all_combinations_of_distributing_k_entities_on_n_positions]") +{ + SECTION("k = 0, n = 0") + { + const std::size_t k = 0; + const std::size_t n = 0; + const std::vector> expected{{}}; + + auto result = determine_all_combinations_of_distributing_k_entities_on_n_positions(k, n); + + REQUIRE(result == expected); + } + + SECTION("k = 1, n = 1") + { + const std::size_t k = 1; + const std::size_t n = 1; + const std::vector> expected{{0}}; + + auto result = determine_all_combinations_of_distributing_k_entities_on_n_positions(k, n); + + REQUIRE(result == expected); + } + + SECTION("k = 2, n = 3") + { + const std::size_t k = 2; + const std::size_t n = 3; + const std::vector> expected{{0, 1}, {0, 2}, {1, 2}}; + + auto result = determine_all_combinations_of_distributing_k_entities_on_n_positions(k, n); + + REQUIRE(result == expected); + } + + SECTION("k = 3, n = 5") + { + const std::size_t k = 3; + const std::size_t n = 5; + const std::vector> expected{{0, 1, 2}, {0, 1, 3}, {0, 1, 4}, {0, 2, 3}, {0, 2, 4}, + {0, 3, 4}, {1, 2, 3}, {1, 2, 4}, {1, 3, 4}, {2, 3, 4}}; + + auto result = determine_all_combinations_of_distributing_k_entities_on_n_positions(k, n); + + REQUIRE(result == expected); + } + + SECTION("k = 0, n = 5") + { + const std::size_t k = 0; + const std::size_t n = 5; + const std::vector> expected{{}}; + + auto result = determine_all_combinations_of_distributing_k_entities_on_n_positions(k, n); + + REQUIRE(result == expected); + } + + SECTION("k = 5, n = 5") + { + const std::size_t k = 5; + const std::size_t n = 5; + const std::vector> expected{{0, 1, 2, 3, 4}}; + + auto result = determine_all_combinations_of_distributing_k_entities_on_n_positions(k, n); + + REQUIRE(result == expected); + } + + SECTION("k > n (invalid case)") + { + const std::size_t k = 6; + const std::size_t n = 5; + const std::vector> expected{}; + + auto result = determine_all_combinations_of_distributing_k_entities_on_n_positions(k, n); + + REQUIRE(result == expected); + } +}