Skip to content
Snippets Groups Projects
Commit 6b1c24b6 authored by Oscar Gustafsson's avatar Oscar Gustafsson :bicyclist:
Browse files

Remove C-based simulator for now

parent e52f9cab
No related branches found
No related tags found
No related merge requests found
Pipeline #93181 failed
Showing
with 0 additions and 2314 deletions
...@@ -13,8 +13,6 @@ before_script: ...@@ -13,8 +13,6 @@ before_script:
# - export CXXFLAGS='--coverage' # - export CXXFLAGS='--coverage'
# Install without dependencies to make sure that requirements.txt is up-to-date # Install without dependencies to make sure that requirements.txt is up-to-date
- pip install --no-deps -ve . - pip install --no-deps -ve .
# Move file, but should be handled by installation
- mv _b_asic* b_asic
- pip show b_asic - pip show b_asic
- export QT_API=$QT_API - export QT_API=$QT_API
# Install test dependencies # Install test dependencies
......
cmake_minimum_required(VERSION 3.8)
project("B-ASIC"
VERSION 1.0.0
DESCRIPTION "Better ASIC Toolbox for Python 3"
LANGUAGES C CXX)
option(ASIC_USE_FETCHCONTENT "Automatically download dependencies" ON)
option(ASIC_USE_CLANG_TIDY "Use clang-tidy for static analysis" OFF)
option(ASIC_BUILDING_PYTHON_DISTRIBUTION "Don't copy compiled binaries to project directory" OFF)
set(LIBRARY_NAME "b_asic") # Name of the python library directory.
set(TARGET_NAME "_${LIBRARY_NAME}") # Name of this extension module.
# Find dependencies.
if(ASIC_USE_FETCHCONTENT)
add_subdirectory(dependencies)
else()
find_package(fmt REQUIRED)
find_package(pybind11 CONFIG REQUIRED)
endif()
# Set output directory for compiled binaries.
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
include(GNUInstallDirs)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_INSTALL_LIBDIR}")
endif()
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
set(CMAKE_PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
set(CMAKE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
# Add files to be compiled into Python module.
pybind11_add_module("${TARGET_NAME}"
# Main files.
"${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation.cpp"
# For DOD simulation.
"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/compile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/run.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/simulation.cpp"
# For OOP simulation (see legacy folder).
#"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/custom_operation.cpp"
#"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/operation.cpp"
#"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/signal_flow_graph.cpp"
#"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/simulation.cpp"
#"${CMAKE_CURRENT_SOURCE_DIR}/src/simulation/special_operations.cpp"
)
# Include headers.
target_include_directories("${TARGET_NAME}" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
# Use C++17.
target_compile_features("${TARGET_NAME}" PRIVATE cxx_std_17)
# Set compiler-specific options using generator expressions.
target_compile_options("${TARGET_NAME}" PRIVATE
$<$<CXX_COMPILER_ID:GNU>: -std=c++17 -Wall -Wextra -Wpedantic -Werror -Wno-psabi $<$<CONFIG:Debug>:-Og -g> $<$<CONFIG:Release>:-O3> $<$<CONFIG:MinSizeRel>:-Os> $<$<CONFIG:RelWithDebInfo>:-O3 -g>>
$<$<CXX_COMPILER_ID:Clang>: -std=c++17 -Wall -Wextra -Wpedantic -Werror $<$<CONFIG:Debug>:-Og -g> $<$<CONFIG:Release>:-O3> $<$<CONFIG:MinSizeRel>:-Os> $<$<CONFIG:RelWithDebInfo>:-O3 -g>>
$<$<CXX_COMPILER_ID:MSVC>: /std:c++17 /W3 /permissive- /WX /wd4996 /utf-8 $<$<CONFIG:Debug>:/Od> $<$<CONFIG:Release>:/Ot> $<$<CONFIG:MinSizeRel>:/Os> $<$<CONFIG:RelWithDebInfo>:/Ot /Od>>)
# Add libraries. Note: pybind11 is already added in pybind11_add_module.
if(ASIC_USE_FETCHCONTENT)
target_link_libraries("${TARGET_NAME}" PRIVATE
dependency_fmt)
else()
target_link_libraries("${TARGET_NAME}" PRIVATE
$<TARGET_NAME_IF_EXISTS:fmt::fmt-header-only>
$<$<NOT:$<TARGET_EXISTS:fmt::fmt-header-only>>:fmt::fmt>)
endif()
# Set up clang-tidy.
if(ASIC_USE_CLANG_TIDY)
find_program(CLANG_TIDY NAMES clang-tidy REQUIRED)
set_property(TARGET "${TARGET_NAME}" PROPERTY CXX_CLANG_TIDY ${CLANG_TIDY})
if(MSVC)
set_target_properties("${TARGET_NAME}" PROPERTIES
VS_GLOBAL_RunCodeAnalysis true
VS_GLOBAL_EnableMicrosoftCodeAnalysis false
VS_GLOBAL_EnableClangTidyCodeAnalysis true
VS_GLOBAL_ClangTidyToolExe "${CLANG_TIDY}")
endif()
endif()
# Copy binaries to project folder for debugging during development.
if(NOT ASIC_BUILDING_PYTHON_DISTRIBUTION)
add_custom_target(copy_binaries ALL
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${TARGET_NAME}>" "${CMAKE_CURRENT_LIST_DIR}/${LIBRARY_NAME}"
COMMENT "Copying binaries to ${CMAKE_CURRENT_LIST_DIR}/${LIBRARY_NAME}"
DEPENDS "${TARGET_NAME}")
endif()
"""B-ASIC - Better ASIC Toolbox. """B-ASIC - Better ASIC Toolbox.
ASIC toolbox that simplifies circuit design and optimization. ASIC toolbox that simplifies circuit design and optimization.
""" """
# Extension module (C++).
# NOTE: If this import gives an error,
# make sure the C++ module has been compiled and installed properly.
# See the included README.md for more information on how to build/install.
from b_asic._b_asic import *
# Python modules. # Python modules.
from b_asic.core_operations import * from b_asic.core_operations import *
from b_asic.graph_component import * from b_asic.graph_component import *
......
import os
import shutil
import subprocess
import sys
import setuptools import setuptools
from setuptools import Extension
from setuptools.command.build_ext import build_ext
class CMakeExtension(Extension):
def __init__(self, name, sourcedir=""):
super().__init__(name, sources=[])
self.sourcedir = os.path.abspath(sourcedir)
class CMakeBuild(build_ext):
def build_extension(self, ext):
CMAKE_EXE = os.environ.get(
"CMAKE_EXE", shutil.which("cmake3") or shutil.which("cmake")
)
if not isinstance(ext, CMakeExtension):
return super().build_extension(ext)
if not CMAKE_EXE:
raise RuntimeError(
f"Cannot build extension {ext.name}: CMake executable not "
"found! Set the CMAKE_EXE environment variable or update your "
"path."
)
cmake_build_type = "Debug" if self.debug else "Release"
cmake_output_dir = os.path.abspath(
os.path.dirname(self.get_ext_fullpath(ext.name))
)
cmake_configure_argv = [
CMAKE_EXE,
ext.sourcedir,
"-DASIC_BUILDING_PYTHON_DISTRIBUTION=ON",
"-DCMAKE_BUILD_TYPE=" + cmake_build_type,
"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + cmake_output_dir,
"-DPYTHON_EXECUTABLE=" + sys.executable,
]
cmake_build_argv = [
CMAKE_EXE,
"--build",
".",
"--config",
cmake_build_type,
]
if not os.path.exists(self.build_temp):
os.makedirs(self.build_temp)
env = os.environ.copy()
print(f"=== Configuring {ext.name} ===")
print(f"Temp dir: {self.build_temp}")
print(f"Output dir: {cmake_output_dir}")
subprocess.check_call(cmake_configure_argv, cwd=self.build_temp, env=env)
print(f"=== Building {ext.name} ===")
print(f"Temp dir: {self.build_temp}")
print(f"Output dir: {cmake_output_dir}")
print(f"Build type: {cmake_build_type}")
subprocess.check_call(cmake_build_argv, cwd=self.build_temp, env=env)
print()
setuptools.setup( setuptools.setup(
author=( author=(
...@@ -81,6 +13,4 @@ setuptools.setup( ...@@ -81,6 +13,4 @@ setuptools.setup(
" andbo467@student.liu.se, mikael.henriksson@liu.se, frans.skarman@liu.se," " andbo467@student.liu.se, mikael.henriksson@liu.se, frans.skarman@liu.se,"
" petter.kallstrom@liu.se, olle.hansson@liu.se" " petter.kallstrom@liu.se, olle.hansson@liu.se"
), ),
ext_modules=[CMakeExtension("_b_asic")],
cmdclass={"build_ext": CMakeBuild},
) )
#ifndef ASIC_ALGORITHM_HPP
#define ASIC_ALGORITHM_HPP
#include <cstddef>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
namespace asic {
namespace detail {
template <typename Reference>
class arrow_proxy final {
public:
template <typename Ref>
constexpr explicit arrow_proxy(Ref&& r)
: m_r(std::forward<Ref>(r)) {}
Reference* operator->() {
return std::addressof(m_r);
}
private:
Reference m_r;
};
template <typename T>
class range_view final {
public:
class iterator final {
public:
using difference_type = std::ptrdiff_t;
using value_type = T const;
using reference = value_type&;
using pointer = value_type*;
using iterator_category = std::random_access_iterator_tag;
constexpr iterator() noexcept = default;
constexpr explicit iterator(T value) noexcept
: m_value(value) {}
[[nodiscard]] constexpr bool operator==(iterator const& other) const noexcept {
return m_value == other.m_value;
}
[[nodiscard]] constexpr bool operator!=(iterator const& other) const noexcept {
return m_value != other.m_value;
}
[[nodiscard]] constexpr bool operator<(iterator const& other) const noexcept {
return m_value < other.m_value;
}
[[nodiscard]] constexpr bool operator>(iterator const& other) const noexcept {
return m_value > other.m_value;
}
[[nodiscard]] constexpr bool operator<=(iterator const& other) const noexcept {
return m_value <= other.m_value;
}
[[nodiscard]] constexpr bool operator>=(iterator const& other) const noexcept {
return m_value >= other.m_value;
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return m_value;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return std::addressof(**this);
}
constexpr iterator& operator++() noexcept {
++m_value;
return *this;
}
constexpr iterator operator++(int) noexcept {
return iterator{m_value++};
}
constexpr iterator& operator--() noexcept {
--m_value;
return *this;
}
constexpr iterator operator--(int) noexcept {
return iterator{m_value--};
}
constexpr iterator& operator+=(difference_type n) noexcept {
m_value += n;
return *this;
}
constexpr iterator& operator-=(difference_type n) noexcept {
m_value -= n;
return *this;
}
[[nodiscard]] constexpr T operator[](difference_type n) noexcept {
return m_value + static_cast<T>(n);
}
[[nodiscard]] constexpr friend iterator operator+(iterator const& lhs, difference_type rhs) noexcept {
return iterator{lhs.m_value + rhs};
}
[[nodiscard]] constexpr friend iterator operator+(difference_type lhs, iterator const& rhs) noexcept {
return iterator{lhs + rhs.m_value};
}
[[nodiscard]] constexpr friend iterator operator-(iterator const& lhs, difference_type rhs) noexcept {
return iterator{lhs.m_value - rhs};
}
[[nodiscard]] constexpr friend difference_type operator-(iterator const& lhs, iterator const& rhs) noexcept {
return static_cast<difference_type>(lhs.m_value - rhs.m_value);
}
private:
T m_value{};
};
using sentinel = iterator;
template <typename First, typename Last>
constexpr range_view(First&& first, Last&& last) noexcept
: m_begin(std::forward<First>(first))
, m_end(std::forward<Last>(last)) {}
[[nodiscard]] constexpr iterator begin() const noexcept {
return m_begin;
}
[[nodiscard]] constexpr sentinel end() const noexcept {
return m_end;
}
private:
iterator m_begin;
sentinel m_end;
};
template <typename Range, typename Iterator, typename Sentinel>
class enumerate_view final {
public:
using sentinel = Sentinel;
class iterator final {
public:
using difference_type = typename std::iterator_traits<Iterator>::difference_type;
using value_type = typename std::iterator_traits<Iterator>::value_type;
using reference = std::pair<std::size_t const&, decltype(*std::declval<Iterator const>())>;
using pointer = arrow_proxy<reference>;
using iterator_category =
std::common_type_t<typename std::iterator_traits<Iterator>::iterator_category, std::bidirectional_iterator_tag>;
constexpr iterator() = default;
constexpr iterator(Iterator it, std::size_t index)
: m_it(std::move(it))
, m_index(index) {}
[[nodiscard]] constexpr bool operator==(iterator const& other) const {
return m_it == other.m_it;
}
[[nodiscard]] constexpr bool operator!=(iterator const& other) const {
return m_it != other.m_it;
}
[[nodiscard]] constexpr bool operator==(sentinel const& other) const {
return m_it == other;
}
[[nodiscard]] constexpr bool operator!=(sentinel const& other) const {
return m_it != other;
}
[[nodiscard]] constexpr reference operator*() const {
return reference{m_index, *m_it};
}
[[nodiscard]] constexpr pointer operator->() const {
return pointer{**this};
}
constexpr iterator& operator++() {
++m_it;
++m_index;
return *this;
}
constexpr iterator operator++(int) {
return iterator{m_it++, m_index++};
}
constexpr iterator& operator--() {
--m_it;
--m_index;
return *this;
}
constexpr iterator operator--(int) {
return iterator{m_it--, m_index--};
}
private:
Iterator m_it;
std::size_t m_index = 0;
};
template <typename R>
constexpr explicit enumerate_view(R&& range)
: m_range(std::forward<R>(range)) {}
[[nodiscard]] constexpr iterator begin() const {
return iterator{std::begin(m_range), 0};
}
[[nodiscard]] constexpr sentinel end() const {
return std::end(m_range);
}
private:
Range m_range;
};
template <typename Range1, typename Range2, typename Iterator1, typename Iterator2, typename Sentinel1, typename Sentinel2>
class zip_view final {
public:
using sentinel = std::pair<Sentinel1, Sentinel2>;
class iterator final {
public:
using difference_type = std::common_type_t<typename std::iterator_traits<Iterator1>::difference_type,
typename std::iterator_traits<Iterator2>::difference_type>;
using value_type =
std::pair<typename std::iterator_traits<Iterator1>::value_type, typename std::iterator_traits<Iterator2>::value_type>;
using reference = std::pair<decltype(*std::declval<Iterator1 const>()), decltype(*std::declval<Iterator2 const>())>;
using pointer = arrow_proxy<reference>;
using iterator_category =
std::common_type_t<typename std::iterator_traits<Iterator1>::iterator_category,
typename std::iterator_traits<Iterator2>::iterator_category, std::bidirectional_iterator_tag>;
constexpr iterator() = default;
constexpr iterator(Iterator1 it1, Iterator2 it2)
: m_it1(std::move(it1))
, m_it2(std::move(it2)) {}
[[nodiscard]] constexpr bool operator==(iterator const& other) const {
return m_it1 == other.m_it1 && m_it2 == other.m_it2;
}
[[nodiscard]] constexpr bool operator!=(iterator const& other) const {
return !(*this == other);
}
[[nodiscard]] constexpr bool operator==(sentinel const& other) const {
return m_it1 == other.first || m_it2 == other.second;
}
[[nodiscard]] constexpr bool operator!=(sentinel const& other) const {
return !(*this == other);
}
[[nodiscard]] constexpr reference operator*() const {
return reference{*m_it1, *m_it2};
}
[[nodiscard]] constexpr pointer operator->() const {
return pointer{**this};
}
constexpr iterator& operator++() {
++m_it1;
++m_it2;
return *this;
}
constexpr iterator operator++(int) {
return iterator{m_it1++, m_it2++};
}
constexpr iterator& operator--() {
--m_it1;
--m_it2;
return *this;
}
constexpr iterator operator--(int) {
return iterator{m_it1--, m_it2--};
}
private:
Iterator1 m_it1;
Iterator2 m_it2;
};
template <typename R1, typename R2>
constexpr zip_view(R1&& range1, R2&& range2)
: m_range1(std::forward<R1>(range1))
, m_range2(std::forward<R2>(range2)) {}
[[nodiscard]] constexpr iterator begin() const {
return iterator{std::begin(m_range1), std::begin(m_range2)};
}
[[nodiscard]] constexpr sentinel end() const {
return sentinel{std::end(m_range1), std::end(m_range2)};
}
private:
Range1 m_range1;
Range2 m_range2;
};
} // namespace detail
template <typename First, typename Last, typename T = std::remove_cv_t<std::remove_reference_t<First>>>
[[nodiscard]] constexpr auto range(First&& first, Last&& last) {
return detail::range_view<T>{std::forward<First>(first), std::forward<Last>(last)};
}
template <typename Last, typename T = std::remove_cv_t<std::remove_reference_t<Last>>>
[[nodiscard]] constexpr auto range(Last&& last) {
return detail::range_view<T>{T{}, std::forward<Last>(last)};
}
template <typename Range, typename Iterator = decltype(std::begin(std::declval<Range>())),
typename Sentinel = decltype(std::end(std::declval<Range>()))>
[[nodiscard]] constexpr auto enumerate(Range&& range) {
return detail::enumerate_view<Range, Iterator, Sentinel>{std::forward<Range>(range)};
}
template <typename Range1, typename Range2, typename Iterator1 = decltype(std::begin(std::declval<Range1>())),
typename Iterator2 = decltype(std::begin(std::declval<Range2>())),
typename Sentinel1 = decltype(std::end(std::declval<Range1>())), typename Sentinel2 = decltype(std::end(std::declval<Range2>()))>
[[nodiscard]] constexpr auto zip(Range1&& range1, Range2&& range2) {
return detail::zip_view<Range1, Range2, Iterator1, Iterator2, Sentinel1, Sentinel2>{std::forward<Range1>(range1),
std::forward<Range2>(range2)};
}
} // namespace asic
#endif // ASIC_ALGORITHM_HPP
#ifndef ASIC_DEBUG_HPP
#define ASIC_DEBUG_HPP
#ifndef NDEBUG
#ifndef ASIC_ENABLE_DEBUG_LOGGING
#define ASIC_ENABLE_DEBUG_LOGGING 1
#endif
#ifndef ASIC_ENABLE_ASSERTS
#define ASIC_ENABLE_ASSERTS 1
#endif
#else
#ifndef ASIC_ENABLE_DEBUG_LOGGING
#define ASIC_ENABLE_DEBUG_LOGGING 0
#endif
#ifndef ASIC_ENABLE_ASSERTS
#define ASIC_ENABLE_ASSERTS 0
#endif
#endif // NDEBUG
#if ASIC_ENABLE_DEBUG_LOGGING
#include <filesystem>
#include <fmt/format.h>
#include <fstream>
#include <ostream>
#include <string_view>
#include <utility>
#endif // ASIC_ENABLE_DEBUG_LOGGING
#if ASIC_ENABLE_ASSERTS
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fmt/format.h>
#include <string_view>
#endif // ASIC_ENABLE_ASSERTS
namespace asic {
constexpr auto debug_log_filename = "_b_asic_debug_log.txt";
namespace detail {
#if ASIC_ENABLE_DEBUG_LOGGING
inline void log_debug_msg_string(std::string_view file, int line, std::string_view string) {
static auto log_file = std::ofstream{debug_log_filename, std::ios::trunc};
log_file << fmt::format("{:<40}: {}", fmt::format("{}:{}", std::filesystem::path{file}.filename().generic_string(), line), string)
<< std::endl;
}
template <typename Format, typename... Args>
inline void log_debug_msg(std::string_view file, int line, Format&& format, Args&&... args) {
log_debug_msg_string(file, line, fmt::format(std::forward<Format>(format), std::forward<Args>(args)...));
}
#endif // ASIC_ENABLE_DEBUG_LOGGING
#if ASIC_ENABLE_ASSERTS
inline void fail_assert(std::string_view file, int line, std::string_view condition_string) {
#if ASIC_ENABLE_DEBUG_LOGGING
log_debug_msg(file, line, "Assertion failed: {}", condition_string);
#endif // ASIC_ENABLE_DEBUG_LOGGING
fmt::print(stderr, "{}:{}: Assertion failed: {}\n", std::filesystem::path{file}.filename().generic_string(), line, condition_string);
std::abort();
}
template <typename BoolConvertible>
inline void check_assert(std::string_view file, int line, std::string_view condition_string, BoolConvertible&& condition) {
if (!static_cast<bool>(condition)) {
fail_assert(file, line, condition_string);
}
}
#endif // ASIC_ENABLE_ASSERTS
} // namespace detail
} // namespace asic
#if ASIC_ENABLE_DEBUG_LOGGING
#define ASIC_DEBUG_MSG(...) (asic::detail::log_debug_msg(__FILE__, __LINE__, __VA_ARGS__))
#else
#define ASIC_DEBUG_MSG(...) ((void)0)
#endif // ASIC_ENABLE_DEBUG_LOGGING
#if ASIC_ENABLE_ASSERTS
#define ASIC_ASSERT(condition) (asic::detail::check_assert(__FILE__, __LINE__, #condition, (condition)))
#else
#define ASIC_ASSERT(condition) ((void)0)
#endif
#endif // ASIC_DEBUG_HPP
#include "simulation.hpp"
#define NOMINMAX
#include <pybind11/pybind11.h>
PYBIND11_MODULE(_b_asic, module) { // NOLINT
module.doc() = "Better ASIC Toolbox Extension Module.";
asic::define_simulation_class(module);
}
#ifndef ASIC_NUMBER_HPP
#define ASIC_NUMBER_HPP
#define NOMINMAX
#include <complex>
#include <pybind11/complex.h>
namespace asic {
using number = std::complex<double>;
} // namespace asic
#endif // ASIC_NUMBER_HPP
#include "simulation.hpp"
#include "simulation/simulation.hpp"
namespace py = pybind11;
namespace asic {
void define_simulation_class(pybind11::module& module) {
// clang-format off
py::class_<simulation>(module, "FastSimulation")
.def(py::init<py::handle>(),
py::arg("sfg"),
"SFG Constructor.")
.def(py::init<py::handle, std::optional<std::vector<std::optional<input_provider_type>>>>(),
py::arg("sfg"), py::arg("input_providers"),
"SFG Constructor.")
.def("set_input", &simulation::set_input,
py::arg("index"), py::arg("input_provider"),
"Set the input function used to get values for the specific input at the given index to the internal SFG.")
.def("set_inputs", &simulation::set_inputs,
py::arg("input_providers"),
"Set the input functions used to get values for the inputs to the internal SFG.")
.def("step", &simulation::step,
py::arg("save_results") = true, py::arg("bits_override") = py::none{}, py::arg("quantize") = true,
"Run one iteration of the simulation and return the resulting output values.")
.def("run_until", &simulation::run_until,
py::arg("iteration"), py::arg("save_results") = true, py::arg("bits_override") = py::none{}, py::arg("quantize") = true,
"Run the simulation until its iteration is greater than or equal to the given iteration\n"
"and return the output values of the last iteration.")
.def("run_for", &simulation::run_for,
py::arg("iterations"), py::arg("save_results") = true, py::arg("bits_override") = py::none{}, py::arg("quantize") = true,
"Run a given number of iterations of the simulation and return the output values of the last iteration.")
.def("run", &simulation::run,
py::arg("save_results") = true, py::arg("bits_override") = py::none{}, py::arg("quantize") = true,
"Run the simulation until the end of its input arrays and return the output values of the last iteration.")
.def_property_readonly("iteration", &simulation::iteration,
"Get the current iteration number of the simulation.")
.def_property_readonly("results", &simulation::results,
"Get a mapping from result keys to numpy arrays containing all results, including intermediate values,\n"
"calculated for each iteration up until now that was run with save_results enabled.\n"
"The mapping is indexed using the key() method of Operation with the appropriate output index.\n"
"Example result after 3 iterations: {\"c1\": [3, 6, 7], \"c2\": [4, 5, 5], \"bfly1.0\": [7, 0, 0], \"bfly1.1\": [-1, 0, 2], \"0\": [7, -2, -1]}")
.def("clear_results", &simulation::clear_results,
"Clear all results that were saved until now.")
.def("clear_state", &simulation::clear_state,
"Clear all current state of the simulation, except for the results and iteration.");
// clang-format on
}
} // namespace asic
#ifndef ASIC_SIMULATION_HPP
#define ASIC_SIMULATION_HPP
#define NOMINMAX
#include <pybind11/pybind11.h>
namespace asic {
void define_simulation_class(pybind11::module& module);
} // namespace asic
#endif // ASIC_SIMULATION_HPP
#include "compile.hpp"
#include "../algorithm.hpp"
#include "../debug.hpp"
#include "../span.hpp"
#include "format_code.hpp"
#define NOMINMAX
#include <Python.h>
#include <fmt/format.h>
#include <limits>
#include <optional>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <utility>
namespace py = pybind11;
namespace asic {
[[nodiscard]] static result_key key_base(py::handle op, std::string_view prefix) {
auto const graph_id = op.attr("graph_id").cast<std::string_view>();
return (prefix.empty()) ? result_key{graph_id} : fmt::format("{}.{}", prefix, graph_id);
}
[[nodiscard]] static result_key key_of_output(py::handle op, std::size_t output_index, std::string_view prefix) {
auto base = key_base(op, prefix);
if (base.empty()) {
return fmt::to_string(output_index);
}
if (op.attr("output_count").cast<std::size_t>() == 1) {
return base;
}
return fmt::format("{}.{}", base, output_index);
}
class compiler final {
public:
simulation_code compile(py::handle sfg) {
ASIC_DEBUG_MSG("Compiling code...");
this->initialize_code(sfg.attr("input_count").cast<std::size_t>(), sfg.attr("output_count").cast<std::size_t>());
auto deferred_delays = delay_queue{};
this->add_outputs(sfg, deferred_delays);
this->add_deferred_delays(std::move(deferred_delays));
this->resolve_invalid_result_indices();
ASIC_DEBUG_MSG("Compiled code:\n{}\n", format_compiled_simulation_code(m_code));
return std::move(m_code);
}
private:
struct sfg_info final {
py::handle sfg;
std::size_t prefix_length;
sfg_info(py::handle sfg, std::size_t prefix_length)
: sfg(sfg)
, prefix_length(prefix_length) {}
[[nodiscard]] std::size_t find_input_operation_index(py::handle op) const {
for (auto const& [i, in] : enumerate(sfg.attr("input_operations"))) {
if (in.is(op)) {
return i;
}
}
throw py::value_error{"Stray Input operation in simulation SFG"};
}
};
using sfg_info_stack = std::vector<sfg_info>;
using delay_queue = std::vector<std::tuple<std::size_t, py::handle, std::string, sfg_info_stack>>;
using added_output_cache = std::unordered_set<PyObject const*>;
using added_result_cache = std::unordered_map<PyObject const*, result_index_type>;
using added_custom_operation_cache = std::unordered_map<PyObject const*, std::size_t>;
static constexpr auto no_result_index = std::numeric_limits<result_index_type>::max();
void initialize_code(std::size_t input_count, std::size_t output_count) {
m_code.required_stack_size = 0;
m_code.input_count = input_count;
m_code.output_count = output_count;
}
void add_outputs(py::handle sfg, delay_queue& deferred_delays) {
for (auto const i : range(m_code.output_count)) {
this->add_operation_output(sfg, i, std::string_view{}, sfg_info_stack{}, deferred_delays);
}
}
void add_deferred_delays(delay_queue&& deferred_delays) {
while (!deferred_delays.empty()) {
auto new_deferred_delays = delay_queue{};
for (auto const& [delay_index, op, prefix, sfg_stack] : deferred_delays) {
this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
this->add_instruction(instruction_type::update_delay, no_result_index, -1).index = delay_index;
}
deferred_delays = new_deferred_delays;
}
}
void resolve_invalid_result_indices() {
for (auto& instruction : m_code.instructions) {
if (instruction.result_index == no_result_index) {
instruction.result_index = static_cast<result_index_type>(m_code.result_keys.size());
}
}
}
[[nodiscard]] static sfg_info_stack push_sfg(sfg_info_stack const& sfg_stack, py::handle sfg, std::size_t prefix_length) {
auto const new_size = static_cast<std::size_t>(sfg_stack.size() + 1);
auto new_sfg_stack = sfg_info_stack{};
new_sfg_stack.reserve(new_size);
for (auto const& info : sfg_stack) {
new_sfg_stack.push_back(info);
}
new_sfg_stack.emplace_back(sfg, prefix_length);
return new_sfg_stack;
}
[[nodiscard]] static sfg_info_stack pop_sfg(sfg_info_stack const& sfg_stack) {
ASIC_ASSERT(!sfg_stack.empty());
auto const new_size = static_cast<std::size_t>(sfg_stack.size() - 1);
auto new_sfg_stack = sfg_info_stack{};
new_sfg_stack.reserve(new_size);
for (auto const& info : span{sfg_stack}.first(new_size)) {
new_sfg_stack.push_back(info);
}
return new_sfg_stack;
}
instruction& add_instruction(instruction_type type, result_index_type result_index, std::ptrdiff_t stack_diff) {
m_stack_depth += stack_diff;
if (m_stack_depth < 0) {
throw py::value_error{"Detected input/output count mismatch in simulation SFG"};
}
if (auto const stack_size = static_cast<std::size_t>(m_stack_depth); stack_size > m_code.required_stack_size) {
m_code.required_stack_size = stack_size;
}
auto& instruction = m_code.instructions.emplace_back();
instruction.type = type;
instruction.result_index = result_index;
return instruction;
}
[[nodiscard]] std::optional<result_index_type> begin_operation_output(py::handle op, std::size_t output_index,
std::string_view prefix) {
auto* const pointer = op.attr("outputs")[py::int_{output_index}].ptr();
if (m_incomplete_outputs.count(pointer) != 0) {
// Make sure the output doesn't depend on its own value, unless it's a delay operation.
if (op.attr("type_name")().cast<std::string_view>() != "t") {
throw py::value_error{"Direct feedback loop detected in simulation SFG"};
}
}
// Try to add a new result.
auto const [it, inserted] = m_added_results.try_emplace(pointer, static_cast<result_index_type>(m_code.result_keys.size()));
if (inserted) {
if (m_code.result_keys.size() >= static_cast<std::size_t>(std::numeric_limits<result_index_type>::max())) {
throw py::value_error{fmt::format("Simulation SFG requires too many outputs to be stored (limit: {})",
std::numeric_limits<result_index_type>::max())};
}
m_code.result_keys.push_back(key_of_output(op, output_index, prefix));
m_incomplete_outputs.insert(pointer);
return it->second;
}
// If the result has already been added, we re-use the old result and
// return std::nullopt to indicate that we don't need to add all the required instructions again.
this->add_instruction(instruction_type::push_result, it->second, 1).index = static_cast<std::size_t>(it->second);
return std::nullopt;
}
void end_operation_output(py::handle op, std::size_t output_index) {
auto* const pointer = op.attr("outputs")[py::int_{output_index}].ptr();
[[maybe_unused]] auto const erased = m_incomplete_outputs.erase(pointer);
ASIC_ASSERT(erased == 1);
}
[[nodiscard]] std::size_t try_add_custom_operation(py::handle op) {
auto const [it, inserted] = m_added_custom_operations.try_emplace(op.ptr(), m_added_custom_operations.size());
if (inserted) {
auto& custom_operation = m_code.custom_operations.emplace_back();
custom_operation.evaluate_output = op.attr("evaluate_output");
custom_operation.input_count = op.attr("input_count").cast<std::size_t>();
custom_operation.output_count = op.attr("output_count").cast<std::size_t>();
}
return it->second;
}
[[nodiscard]] std::size_t add_delay_info(number initial_value, result_index_type result_index) {
auto const delay_index = m_code.delays.size();
auto& delay = m_code.delays.emplace_back();
delay.initial_value = initial_value;
delay.result_index = result_index;
return delay_index;
}
void add_source(py::handle op, std::size_t input_index, std::string_view prefix, sfg_info_stack const& sfg_stack,
delay_queue& deferred_delays) {
auto const signal = py::object{op.attr("inputs")[py::int_{input_index}].attr("signals")[py::int_{0}]};
auto const src = py::handle{signal.attr("source")};
auto const operation = py::handle{src.attr("operation")};
auto const index = src.attr("index").cast<std::size_t>();
this->add_operation_output(operation, index, prefix, sfg_stack, deferred_delays);
if (!signal.attr("bits").is_none()) {
auto const bits = signal.attr("bits").cast<std::size_t>();
if (bits > 64) {
throw py::value_error{"Cannot quantize to more than 64 bits"};
}
this->add_instruction(instruction_type::quantize, no_result_index, 0).bit_mask = static_cast<std::int64_t>(
(std::int64_t{1} << bits) - 1);
}
}
void add_unary_operation_output(py::handle op, result_index_type result_index, std::string_view prefix, sfg_info_stack const& sfg_stack,
delay_queue& deferred_delays, instruction_type type) {
this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
this->add_instruction(type, result_index, 0);
}
void add_binary_operation_output(py::handle op, result_index_type result_index, std::string_view prefix,
sfg_info_stack const& sfg_stack, delay_queue& deferred_delays, instruction_type type) {
this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
this->add_source(op, 1, prefix, sfg_stack, deferred_delays);
this->add_instruction(type, result_index, -1);
}
void add_operation_output(py::handle op, std::size_t output_index, std::string_view prefix, sfg_info_stack const& sfg_stack,
delay_queue& deferred_delays) {
auto const type_name = op.attr("type_name")().cast<std::string_view>();
if (type_name == "out") {
this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
} else if (auto const result_index = this->begin_operation_output(op, output_index, prefix)) {
if (type_name == "c") {
this->add_instruction(instruction_type::push_constant, *result_index, 1).value = op.attr("value").cast<number>();
} else if (type_name == "add") {
this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::addition);
} else if (type_name == "sub") {
this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::subtraction);
} else if (type_name == "mul") {
this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::multiplication);
} else if (type_name == "div") {
this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::division);
} else if (type_name == "min") {
this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::min);
} else if (type_name == "max") {
this->add_binary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::max);
} else if (type_name == "sqrt") {
this->add_unary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::square_root);
} else if (type_name == "conj") {
this->add_unary_operation_output(
op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::complex_conjugate);
} else if (type_name == "abs") {
this->add_unary_operation_output(op, *result_index, prefix, sfg_stack, deferred_delays, instruction_type::absolute);
} else if (type_name == "cmul") {
this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
this->add_instruction(instruction_type::constant_multiplication, *result_index, 0).value = op.attr("value").cast<number>();
} else if (type_name == "bfly") {
if (output_index == 0) {
this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
this->add_source(op, 1, prefix, sfg_stack, deferred_delays);
this->add_instruction(instruction_type::addition, *result_index, -1);
} else {
this->add_source(op, 0, prefix, sfg_stack, deferred_delays);
this->add_source(op, 1, prefix, sfg_stack, deferred_delays);
this->add_instruction(instruction_type::subtraction, *result_index, -1);
}
} else if (type_name == "in") {
if (sfg_stack.empty()) {
throw py::value_error{"Encountered Input operation outside SFG in simulation"};
}
auto const& info = sfg_stack.back();
auto const input_index = info.find_input_operation_index(op);
if (sfg_stack.size() == 1) {
this->add_instruction(instruction_type::push_input, *result_index, 1).index = input_index;
} else {
this->add_source(info.sfg, input_index, prefix.substr(0, info.prefix_length), pop_sfg(sfg_stack), deferred_delays);
this->add_instruction(instruction_type::forward_value, *result_index, 0);
}
} else if (type_name == "t") {
auto const delay_index = this->add_delay_info(op.attr("initial_value").cast<number>(), *result_index);
deferred_delays.emplace_back(delay_index, op, std::string{prefix}, sfg_stack);
this->add_instruction(instruction_type::push_delay, *result_index, 1).index = delay_index;
} else if (type_name == "sfg") {
auto const output_op = py::handle{op.attr("output_operations")[py::int_{output_index}]};
this->add_source(output_op, 0, key_base(op, prefix), push_sfg(sfg_stack, op, prefix.size()), deferred_delays);
this->add_instruction(instruction_type::forward_value, *result_index, 0);
} else {
auto const custom_operation_index = this->try_add_custom_operation(op);
auto const& custom_operation = m_code.custom_operations[custom_operation_index];
for (auto const i : range(custom_operation.input_count)) {
this->add_source(op, i, prefix, sfg_stack, deferred_delays);
}
auto const custom_source_index = m_code.custom_sources.size();
auto& custom_source = m_code.custom_sources.emplace_back();
custom_source.custom_operation_index = custom_operation_index;
custom_source.output_index = output_index;
auto const stack_diff = std::ptrdiff_t{1} - static_cast<std::ptrdiff_t>(custom_operation.input_count);
this->add_instruction(instruction_type::custom, *result_index, stack_diff).index = custom_source_index;
}
this->end_operation_output(op, output_index);
}
}
simulation_code m_code{};
added_output_cache m_incomplete_outputs{};
added_result_cache m_added_results{};
added_custom_operation_cache m_added_custom_operations{};
std::ptrdiff_t m_stack_depth = 0;
};
simulation_code compile_simulation(pybind11::handle sfg) {
return compiler{}.compile(sfg);
}
} // namespace asic
#ifndef ASIC_SIMULATION_COMPILE_HPP
#define ASIC_SIMULATION_COMPILE_HPP
#include "instruction.hpp"
#define NOMINMAX
#include <cstddef>
#include <pybind11/pybind11.h>
#include <string>
#include <vector>
namespace asic {
using result_key = std::string;
struct simulation_code final {
struct custom_operation final {
// Python function used to evaluate the custom operation.
pybind11::object evaluate_output{};
// Number of inputs that the custom operation takes.
std::size_t input_count = 0;
// Number of outputs that the custom operation gives.
std::size_t output_count = 0;
};
struct custom_source final {
// Index into custom_operations where the custom_operation corresponding to this custom_source is located.
std::size_t custom_operation_index = 0;
// Output index of the custom_operation that this source gets it value from.
std::size_t output_index = 0;
};
struct delay_info final {
// Initial value to set at the start of the simulation.
number initial_value{};
// The result index where the current value should be stored at the start of each iteration.
result_index_type result_index = 0;
};
// Instructions to execute for one full iteration of the simulation.
std::vector<instruction> instructions{};
// Custom operations used by the simulation.
std::vector<custom_operation> custom_operations{};
// Signal sources that use custom operations.
std::vector<custom_source> custom_sources{};
// Info about the delay operations used in the simulation.
std::vector<delay_info> delays{};
// Keys for each result produced by the simulation. The index of the key matches the index of the result in the simulation state.
std::vector<result_key> result_keys{};
// Number of values expected as input to the simulation.
std::size_t input_count = 0;
// Number of values given as output from the simulation. This will be the number of values left on the stack after a full iteration of the simulation has been run.
std::size_t output_count = 0;
// Maximum number of values that need to be able to fit on the stack in order to run a full iteration of the simulation.
std::size_t required_stack_size = 0;
};
[[nodiscard]] simulation_code compile_simulation(pybind11::handle sfg);
} // namespace asic
#endif // ASIC_SIMULATION_COMPILE_HPP
#ifndef ASIC_SIMULATION_FORMAT_CODE_HPP
#define ASIC_SIMULATION_FORMAT_CODE_HPP
#include "../algorithm.hpp"
#include "../debug.hpp"
#include "../number.hpp"
#include "compile.hpp"
#include "instruction.hpp"
#include <fmt/format.h>
#include <string>
namespace asic {
[[nodiscard]] inline std::string format_number(number const& value) {
if (value.imag() == 0) {
return fmt::to_string(value.real());
}
if (value.real() == 0) {
return fmt::format("{}j", value.imag());
}
if (value.imag() < 0) {
return fmt::format("{}-{}j", value.real(), -value.imag());
}
return fmt::format("{}+{}j", value.real(), value.imag());
}
[[nodiscard]] inline std::string format_compiled_simulation_code_result_keys(simulation_code const& code) {
auto result = std::string{};
for (auto const& [i, result_key] : enumerate(code.result_keys)) {
result += fmt::format("{:>2}: \"{}\"\n", i, result_key);
}
return result;
}
[[nodiscard]] inline std::string format_compiled_simulation_code_delays(simulation_code const& code) {
auto result = std::string{};
for (auto const& [i, delay] : enumerate(code.delays)) {
ASIC_ASSERT(delay.result_index < code.result_keys.size());
result += fmt::format("{:>2}: Initial value: {}, Result: {}: \"{}\"\n",
i,
format_number(delay.initial_value),
delay.result_index,
code.result_keys[delay.result_index]);
}
return result;
}
[[nodiscard]] inline std::string format_compiled_simulation_code_instruction(instruction const& instruction) {
// clang-format off
switch (instruction.type) {
case instruction_type::push_input: return fmt::format("push_input inputs[{}]", instruction.index);
case instruction_type::push_result: return fmt::format("push_result results[{}]", instruction.index);
case instruction_type::push_delay: return fmt::format("push_delay delays[{}]", instruction.index);
case instruction_type::push_constant: return fmt::format("push_constant {}", format_number(instruction.value));
case instruction_type::quantize: return fmt::format("quantize {:#018x}", instruction.bit_mask);
case instruction_type::addition: return "addition";
case instruction_type::subtraction: return "subtraction";
case instruction_type::multiplication: return "multiplication";
case instruction_type::division: return "division";
case instruction_type::min: return "min";
case instruction_type::max: return "max";
case instruction_type::square_root: return "square_root";
case instruction_type::complex_conjugate: return "complex_conjugate";
case instruction_type::absolute: return "absolute";
case instruction_type::constant_multiplication: return fmt::format("constant_multiplication {}", format_number(instruction.value));
case instruction_type::update_delay: return fmt::format("update_delay delays[{}]", instruction.index);
case instruction_type::custom: return fmt::format("custom custom_sources[{}]", instruction.index);
case instruction_type::forward_value: return "forward_value";
}
// clang-format on
return std::string{};
}
[[nodiscard]] inline std::string format_compiled_simulation_code_instructions(simulation_code const& code) {
auto result = std::string{};
for (auto const& [i, instruction] : enumerate(code.instructions)) {
auto instruction_string = format_compiled_simulation_code_instruction(instruction);
if (instruction.result_index < code.result_keys.size()) {
instruction_string = fmt::format(
"{:<26} -> {}: \"{}\"", instruction_string, instruction.result_index, code.result_keys[instruction.result_index]);
}
result += fmt::format("{:>2}: {}\n", i, instruction_string);
}
return result;
}
[[nodiscard]] inline std::string format_compiled_simulation_code(simulation_code const& code) {
return fmt::format(
"==============================================\n"
"> Code stats\n"
"==============================================\n"
"Input count: {}\n"
"Output count: {}\n"
"Instruction count: {}\n"
"Required stack size: {}\n"
"Delay count: {}\n"
"Result count: {}\n"
"Custom operation count: {}\n"
"Custom source count: {}\n"
"==============================================\n"
"> Delays\n"
"==============================================\n"
"{}"
"==============================================\n"
"> Result keys\n"
"==============================================\n"
"{}"
"==============================================\n"
"> Instructions\n"
"==============================================\n"
"{}"
"==============================================",
code.input_count,
code.output_count,
code.instructions.size(),
code.required_stack_size,
code.delays.size(),
code.result_keys.size(),
code.custom_operations.size(),
code.custom_sources.size(),
format_compiled_simulation_code_delays(code),
format_compiled_simulation_code_result_keys(code),
format_compiled_simulation_code_instructions(code));
}
} // namespace asic
#endif // ASIC_SIMULATION_FORMAT_CODE_HPP
#ifndef ASIC_SIMULATION_INSTRUCTION_HPP
#define ASIC_SIMULATION_INSTRUCTION_HPP
#include "../number.hpp"
#include <cstddef>
#include <cstdint>
#include <optional>
namespace asic {
enum class instruction_type : std::uint8_t {
push_input, // push(inputs[index])
push_result, // push(results[index])
push_delay, // push(delays[index])
push_constant, // push(value)
quantize, // push(trunc(pop(), bit_mask))
addition, // rhs=pop(), lhs=pop(), push(lhs + rhs)
subtraction, // rhs=pop(), lhs=pop(), push(lhs - rhs)
multiplication, // rhs=pop(), lhs=pop(), push(lhs * rhs)
division, // rhs=pop(), lhs=pop(), push(lhs / rhs)
min, // rhs=pop(), lhs=pop(), push(min(lhs, rhs))
max, // rhs=pop(), lhs=pop(), push(max(lhs, rhs))
square_root, // push(sqrt(pop()))
complex_conjugate, // push(conj(pop()))
absolute, // push(abs(pop()))
constant_multiplication, // push(pop() * value)
update_delay, // delays[index] = pop()
custom, // Custom operation. Uses custom_source[index].
forward_value // Forward the current value on the stack (push(pop()), i.e. do nothing).
};
using result_index_type = std::uint16_t;
struct instruction final {
constexpr instruction() noexcept // NOLINT(cppcoreguidelines-pro-type-member-init)
: index(0) {}
union {
// Index used by push_input, push_result, delay and custom.
std::size_t index;
// Bit mask used by quantize.
std::int64_t bit_mask;
// Constant value used by push_constant and constant_multiplication.
number value;
};
// Index into where the result of the instruction will be stored. If the result should be ignored, this index will be one past the last valid result index.
result_index_type result_index = 0;
// Specifies what kind of operation the instruction should execute.
instruction_type type = instruction_type::forward_value;
};
} // namespace asic
#endif // ASIC_SIMULATION_INSTRUCTION_HPP
#include "run.hpp"
#include "../algorithm.hpp"
#include "../debug.hpp"
#include "format_code.hpp"
#define NOMINMAX
#include <algorithm>
#include <complex>
#include <cstddef>
#include <fmt/format.h>
#include <iterator>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <stdexcept>
namespace py = pybind11;
namespace asic {
[[nodiscard]] static number quantize_value(number value, std::int64_t bit_mask) {
if (value.imag() != 0) {
throw py::type_error{"Complex value cannot be quantized"};
}
return number{static_cast<number::value_type>(static_cast<std::int64_t>(value.real()) & bit_mask)};
}
[[nodiscard]] static std::int64_t setup_truncation_parameters(bool& quantize, std::optional<std::uint8_t>& bits_override) {
if (quantize && bits_override) {
quantize = false; // Ignore quantize instructions, they will be quantized using bits_override instead.
if (*bits_override > 64) {
throw py::value_error{"Cannot quantize to more than 64 bits"};
}
return static_cast<std::int64_t>((std::int64_t{1} << *bits_override) - 1); // Return the bit mask override to use.
}
bits_override.reset(); // Don't use bits_override if quantize is false.
return std::int64_t{};
}
simulation_state run_simulation(simulation_code const& code, span<number const> inputs, span<number> delays,
std::optional<std::uint8_t> bits_override, bool quantize) {
ASIC_ASSERT(inputs.size() == code.input_count);
ASIC_ASSERT(delays.size() == code.delays.size());
ASIC_ASSERT(code.output_count <= code.required_stack_size);
auto state = simulation_state{};
// Setup results.
state.results.resize(code.result_keys.size() + 1); // Add one space to store ignored results.
// Initialize delay results to their current values.
for (auto const& [i, delay] : enumerate(code.delays)) {
state.results[delay.result_index] = delays[i];
}
// Setup stack.
state.stack.resize(code.required_stack_size);
auto* stack_pointer = state.stack.data();
// Utility functions to make the stack manipulation code below more readable.
// Should hopefully be inlined by the compiler.
auto const push = [&](number value) -> void {
ASIC_ASSERT(std::distance(state.stack.data(), stack_pointer) < static_cast<std::ptrdiff_t>(state.stack.size()));
*stack_pointer++ = value;
};
auto const pop = [&]() -> number {
ASIC_ASSERT(std::distance(state.stack.data(), stack_pointer) > std::ptrdiff_t{0});
return *--stack_pointer;
};
auto const peek = [&]() -> number {
ASIC_ASSERT(std::distance(state.stack.data(), stack_pointer) > std::ptrdiff_t{0});
ASIC_ASSERT(std::distance(state.stack.data(), stack_pointer) <= static_cast<std::ptrdiff_t>(state.stack.size()));
return *(stack_pointer - 1);
};
// Check if results should be quantized.
auto const bit_mask_override = setup_truncation_parameters(quantize, bits_override);
// Hot instruction evaluation loop.
for (auto const& instruction : code.instructions) {
ASIC_DEBUG_MSG("Evaluating {}.", format_compiled_simulation_code_instruction(instruction));
// Execute the instruction.
switch (instruction.type) {
case instruction_type::push_input:
push(inputs[instruction.index]);
break;
case instruction_type::push_result:
push(state.results[instruction.index]);
break;
case instruction_type::push_delay:
push(delays[instruction.index]);
break;
case instruction_type::push_constant:
push(instruction.value);
break;
case instruction_type::quantize:
if (quantize) {
push(quantize_value(pop(), instruction.bit_mask));
}
break;
case instruction_type::addition: {
auto const rhs = pop();
auto const lhs = pop();
push(lhs + rhs);
break;
}
case instruction_type::subtraction: {
auto const rhs = pop();
auto const lhs = pop();
push(lhs - rhs);
break;
}
case instruction_type::multiplication: {
auto const rhs = pop();
auto const lhs = pop();
push(lhs * rhs);
break;
}
case instruction_type::division: {
auto const rhs = pop();
auto const lhs = pop();
push(lhs / rhs);
break;
}
case instruction_type::min: {
auto const rhs = pop();
auto const lhs = pop();
if (lhs.imag() != 0 || rhs.imag() != 0) {
throw std::runtime_error{"Min does not support complex numbers."};
}
push(std::min(lhs.real(), rhs.real()));
break;
}
case instruction_type::max: {
auto const rhs = pop();
auto const lhs = pop();
if (lhs.imag() != 0 || rhs.imag() != 0) {
throw std::runtime_error{"Max does not support complex numbers."};
}
push(std::max(lhs.real(), rhs.real()));
break;
}
case instruction_type::square_root:
push(std::sqrt(pop()));
break;
case instruction_type::complex_conjugate:
push(std::conj(pop()));
break;
case instruction_type::absolute:
push(number{std::abs(pop())});
break;
case instruction_type::constant_multiplication:
push(pop() * instruction.value);
break;
case instruction_type::update_delay:
delays[instruction.index] = pop();
break;
case instruction_type::custom: {
using namespace pybind11::literals;
auto const& src = code.custom_sources[instruction.index];
auto const& op = code.custom_operations[src.custom_operation_index];
auto input_values = std::vector<number>{};
input_values.reserve(op.input_count);
for (auto i = std::size_t{0}; i < op.input_count; ++i) {
input_values.push_back(pop());
}
push(op.evaluate_output(src.output_index, std::move(input_values), "quantize"_a = quantize).cast<number>());
break;
}
case instruction_type::forward_value:
// Do nothing, since doing push(pop()) would be pointless.
break;
}
// If we've been given a global override for how many bits to use, always quantize the result.
if (bits_override) {
push(quantize_value(pop(), bit_mask_override));
}
// Store the result.
state.results[instruction.result_index] = peek();
}
// Remove the space that we used for ignored results.
state.results.pop_back();
// Erase the portion of the stack that does not contain the output values.
state.stack.erase(state.stack.begin() + static_cast<std::ptrdiff_t>(code.output_count), state.stack.end());
return state;
}
} // namespace asic
#ifndef ASIC_SIMULATION_RUN_HPP
#define ASIC_SIMULATION_RUN_HPP
#include "../number.hpp"
#include "../span.hpp"
#include "compile.hpp"
#include <cstdint>
#include <vector>
namespace asic {
struct simulation_state final {
std::vector<number> stack{};
std::vector<number> results{};
};
simulation_state run_simulation(simulation_code const& code, span<number const> inputs, span<number> delays,
std::optional<std::uint8_t> bits_override, bool quantize);
} // namespace asic
#endif // ASIC_SIMULATION_RUN_HPP
#include "simulation.hpp"
#include "../algorithm.hpp"
#include "../debug.hpp"
#include "compile.hpp"
#include "run.hpp"
#define NOMINMAX
#include <fmt/format.h>
#include <limits>
#include <pybind11/numpy.h>
#include <utility>
namespace py = pybind11;
namespace asic {
simulation::simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_type>>> input_providers)
: m_code(compile_simulation(sfg))
, m_input_functions(sfg.attr("input_count").cast<std::size_t>(), [](iteration_type) -> number { return number{}; }) {
m_delays.reserve(m_code.delays.size());
for (auto const& delay : m_code.delays) {
m_delays.push_back(delay.initial_value);
}
if (input_providers) {
this->set_inputs(std::move(*input_providers));
}
}
void simulation::set_input(std::size_t index, input_provider_type input_provider) {
if (index >= m_input_functions.size()) {
throw py::index_error{fmt::format("Input index out of range (expected 0-{}, got {})", m_input_functions.size() - 1, index)};
}
if (auto* const callable = std::get_if<input_function_type>(&input_provider)) {
m_input_functions[index] = std::move(*callable);
} else if (auto* const numeric = std::get_if<number>(&input_provider)) {
m_input_functions[index] = [value = *numeric](iteration_type) -> number {
return value;
};
} else if (auto* const list = std::get_if<std::vector<number>>(&input_provider)) {
if (!m_input_length) {
m_input_length = static_cast<iteration_type>(list->size());
} else if (*m_input_length != static_cast<iteration_type>(list->size())) {
throw py::value_error{fmt::format("Inconsistent input length for simulation (was {}, got {})", *m_input_length, list->size())};
}
m_input_functions[index] = [values = std::move(*list)](iteration_type n) -> number {
return values.at(n);
};
}
}
void simulation::set_inputs(
std::vector<std::optional<input_provider_type>> input_providers) { // NOLINT(performance-unnecessary-value-param)
if (input_providers.size() != m_input_functions.size()) {
throw py::value_error{fmt::format(
"Wrong number of inputs supplied to simulation (expected {}, got {})", m_input_functions.size(), input_providers.size())};
}
for (auto&& [i, input_provider] : enumerate(input_providers)) {
if (input_provider) {
this->set_input(i, std::move(*input_provider));
}
}
}
std::vector<number> simulation::step(bool save_results, std::optional<std::uint8_t> bits_override, bool quantize) {
return this->run_for(1, save_results, bits_override, quantize);
}
std::vector<number> simulation::run_until(iteration_type iteration, bool save_results, std::optional<std::uint8_t> bits_override,
bool quantize) {
auto result = std::vector<number>{};
while (m_iteration < iteration) {
ASIC_DEBUG_MSG("Running simulation iteration.");
auto inputs = std::vector<number>(m_code.input_count);
for (auto&& [input, function] : zip(inputs, m_input_functions)) {
input = function(m_iteration);
}
auto state = run_simulation(m_code, inputs, m_delays, bits_override, quantize);
result = std::move(state.stack);
if (save_results) {
m_results.push_back(std::move(state.results));
}
++m_iteration;
}
return result;
}
std::vector<number> simulation::run_for(iteration_type iterations, bool save_results, std::optional<std::uint8_t> bits_override,
bool quantize) {
if (iterations > std::numeric_limits<iteration_type>::max() - m_iteration) {
throw py::value_error("Simulation iteration type overflow!");
}
return this->run_until(m_iteration + iterations, save_results, bits_override, quantize);
}
std::vector<number> simulation::run(bool save_results, std::optional<std::uint8_t> bits_override, bool quantize) {
if (m_input_length) {
return this->run_until(*m_input_length, save_results, bits_override, quantize);
}
throw py::index_error{"Tried to run unlimited simulation"};
}
iteration_type simulation::iteration() const noexcept {
return m_iteration;
}
pybind11::dict simulation::results() const noexcept {
auto results = py::dict{};
if (!m_results.empty()) {
for (auto const& [i, key] : enumerate(m_code.result_keys)) {
auto values = std::vector<number>{};
values.reserve(m_results.size());
for (auto const& result : m_results) {
values.push_back(result[i]);
}
results[py::str{key}] = py::array{static_cast<py::ssize_t>(values.size()), values.data()};
}
}
return results;
}
void simulation::clear_results() noexcept {
m_results.clear();
}
void simulation::clear_state() noexcept {
m_delays.clear();
}
} // namespace asic
#ifndef ASIC_SIMULATION_DOD_HPP
#define ASIC_SIMULATION_DOD_HPP
#include "../number.hpp"
#include "compile.hpp"
#define NOMINMAX
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <pybind11/functional.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <variant>
#include <vector>
namespace asic {
using iteration_type = std::uint32_t;
using input_function_type = std::function<number(iteration_type)>;
using input_provider_type = std::variant<number, std::vector<number>, input_function_type>;
class simulation final {
public:
simulation(pybind11::handle sfg, std::optional<std::vector<std::optional<input_provider_type>>> input_providers = std::nullopt);
void set_input(std::size_t index, input_provider_type input_provider);
void set_inputs(std::vector<std::optional<input_provider_type>> input_providers);
[[nodiscard]] std::vector<number> step(bool save_results, std::optional<std::uint8_t> bits_override, bool quantize);
[[nodiscard]] std::vector<number> run_until(iteration_type iteration, bool save_results, std::optional<std::uint8_t> bits_override,
bool quantize);
[[nodiscard]] std::vector<number> run_for(iteration_type iterations, bool save_results, std::optional<std::uint8_t> bits_override,
bool quantize);
[[nodiscard]] std::vector<number> run(bool save_results, std::optional<std::uint8_t> bits_override, bool quantize);
[[nodiscard]] iteration_type iteration() const noexcept;
[[nodiscard]] pybind11::dict results() const noexcept;
void clear_results() noexcept;
void clear_state() noexcept;
private:
simulation_code m_code;
std::vector<input_function_type> m_input_functions;
std::vector<number> m_delays{};
std::optional<iteration_type> m_input_length{};
iteration_type m_iteration = 0;
std::vector<std::vector<number>> m_results{};
};
} // namespace asic
#endif // ASIC_SIMULATION_DOD_HPP
#ifndef ASIC_SPAN_HPP
#define ASIC_SPAN_HPP
#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <iterator>
#include <limits>
#include <type_traits>
#include <utility>
namespace asic {
constexpr auto dynamic_size = static_cast<std::size_t>(-1);
// C++17-compatible std::span substitute.
template <typename T, std::size_t Size = dynamic_size>
class span;
namespace detail {
template <typename T>
struct is_span_impl : std::false_type {};
template <typename T, std::size_t Size>
struct is_span_impl<span<T, Size>> : std::true_type {};
template <typename T>
struct is_span : is_span_impl<std::remove_cv_t<T>> {};
template <typename T>
constexpr auto is_span_v = is_span<T>::value;
template <typename T>
struct is_std_array_impl : std::false_type {};
template <typename T, std::size_t Size>
struct is_std_array_impl<std::array<T, Size>> : std::true_type {};
template <typename T>
struct is_std_array : is_std_array_impl<std::remove_cv_t<T>> {};
template <typename T>
constexpr auto is_std_array_v = is_std_array<T>::value;
template <std::size_t From, std::size_t To>
struct is_size_convertible : std::bool_constant<From == To || From == dynamic_size || To == dynamic_size> {};
template <std::size_t From, std::size_t To>
constexpr auto is_size_convertible_v = is_size_convertible<From, To>::value;
template <typename From, typename To>
struct is_element_type_convertible : std::bool_constant<std::is_convertible_v<From (*)[], To (*)[]>> {};
template <typename From, typename To>
constexpr auto is_element_type_convertible_v = is_element_type_convertible<From, To>::value;
template <typename T, std::size_t Size>
struct span_base {
using element_type = T;
using pointer = element_type*;
using size_type = std::size_t;
constexpr span_base() noexcept = default;
constexpr span_base(pointer data, [[maybe_unused]] size_type size)
: m_data(data) {
assert(size == Size);
}
template <size_type N>
constexpr span_base(span_base<T, N> other)
: m_data(other.data()) {
static_assert(N == Size || N == dynamic_size);
assert(other.size() == Size);
}
[[nodiscard]] constexpr pointer data() const noexcept {
return m_data;
}
[[nodiscard]] constexpr size_type size() const noexcept {
return Size;
}
private:
pointer m_data = nullptr;
};
template <typename T>
struct span_base<T, dynamic_size> {
using element_type = T;
using pointer = element_type*;
using size_type = std::size_t;
constexpr span_base() noexcept = default;
constexpr span_base(pointer data, size_type size)
: m_data(data)
, m_size(size) {}
template <size_type N>
explicit constexpr span_base(span_base<T, N> other)
: m_data(other.data())
, m_size(other.size()) {}
[[nodiscard]] constexpr pointer data() const noexcept {
return m_data;
}
[[nodiscard]] constexpr size_type size() const noexcept {
return m_size;
}
private:
pointer m_data = nullptr;
size_type m_size = 0;
};
template <typename T, std::size_t Size, std::size_t Offset, std::size_t N>
struct subspan_type {
using type = span<T, (N != dynamic_size) ? N : (Size != dynamic_size) ? Size - Offset : Size>;
};
template <typename T, std::size_t Size, std::size_t Offset, std::size_t Count>
using subspan_type_t = typename subspan_type<T, Size, Offset, Count>::type;
} // namespace detail
template <typename T, std::size_t Size>
class span final : public detail::span_base<T, Size> { // NOLINT(cppcoreguidelines-special-member-functions)
public:
using element_type = typename detail::span_base<T, Size>::element_type;
using pointer = typename detail::span_base<T, Size>::pointer;
using size_type = typename detail::span_base<T, Size>::size_type;
using value_type = std::remove_cv_t<element_type>;
using reference = element_type&;
using iterator = element_type*;
using const_iterator = const element_type*;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
// Default constructor.
constexpr span() noexcept = default;
// Construct from pointer, size.
constexpr span(pointer data, size_type size)
: detail::span_base<T, Size>(data, size) {}
// Copy constructor.
template <typename U, std::size_t N, typename = std::enable_if_t<detail::is_size_convertible_v<N, Size>>,
typename = std::enable_if_t<detail::is_element_type_convertible_v<U, T>>>
constexpr span(span<U, N> const& other)
: span(other.data(), other.size()) {}
// Copy assignment.
constexpr span& operator=(span const&) noexcept = default;
// Destructor.
~span() = default;
// Construct from begin, end.
constexpr span(pointer begin, pointer end)
: span(begin, end - begin) {}
// Construct from C array.
template <std::size_t N>
constexpr span(element_type (&arr)[N]) noexcept
: span(std::data(arr), N) {}
// Construct from std::array.
template <std::size_t N, typename = std::enable_if_t<N != 0>>
constexpr span(std::array<value_type, N>& arr) noexcept
: span(std::data(arr), N) {}
// Construct from empty std::array.
constexpr span(std::array<value_type, 0>&) noexcept
: span() {}
// Construct from const std::array.
template <std::size_t N, typename = std::enable_if_t<N != 0>>
constexpr span(std::array<value_type, N> const& arr) noexcept
: span(std::data(arr), N) {}
// Construct from empty const std::array.
constexpr span(std::array<value_type, 0> const&) noexcept
: span() {}
// Construct from other container.
template <
typename Container, typename = std::enable_if_t<!detail::is_span_v<Container>>,
typename = std::enable_if_t<!detail::is_std_array_v<Container>>, typename = decltype(std::data(std::declval<Container>())),
typename = decltype(std::size(std::declval<Container>())),
typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, pointer>>,
typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, decltype(std::data(std::declval<Container>()))>>>
constexpr span(Container& container)
: span(std::data(container), std::size(container)) {}
// Construct from other const container.
template <
typename Container, typename Element = element_type, typename = std::enable_if_t<std::is_const_v<Element>>,
typename = std::enable_if_t<!detail::is_span_v<Container>>, typename = std::enable_if_t<!detail::is_std_array_v<Container>>,
typename = decltype(std::data(std::declval<Container>())), typename = decltype(std::size(std::declval<Container>())),
typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, pointer>>,
typename = std::enable_if_t<std::is_convertible_v<typename Container::pointer, decltype(std::data(std::declval<Container>()))>>>
constexpr span(Container const& container)
: span(std::data(container), std::size(container)) {}
[[nodiscard]] constexpr iterator begin() const noexcept {
return this->data();
}
[[nodiscard]] constexpr const_iterator cbegin() const noexcept {
return this->data();
}
[[nodiscard]] constexpr iterator end() const noexcept {
return this->data() + this->size();
}
[[nodiscard]] constexpr const_iterator cend() const noexcept {
return this->data() + this->size();
}
[[nodiscard]] constexpr reverse_iterator rbegin() const noexcept {
return std::make_reverse_iterator(this->end());
}
[[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept {
return std::make_reverse_iterator(this->cend());
}
[[nodiscard]] constexpr reverse_iterator rend() const noexcept {
return std::make_reverse_iterator(this->begin());
}
[[nodiscard]] constexpr const_reverse_iterator crend() const noexcept {
return std::make_reverse_iterator(this->cbegin());
}
[[nodiscard]] constexpr reference operator[](size_type i) const noexcept {
assert(i < this->size());
return this->data()[i];
}
[[nodiscard]] constexpr reference operator()(size_type i) const noexcept {
assert(i < this->size());
return this->data()[i];
}
[[nodiscard]] constexpr size_type size_bytes() const noexcept {
return this->size() * sizeof(element_type);
}
[[nodiscard]] constexpr bool empty() const noexcept {
return this->size() == 0;
}
[[nodiscard]] constexpr reference front() const noexcept {
assert(!this->empty());
return this->data()[0];
}
[[nodiscard]] constexpr reference back() const noexcept {
assert(!this->empty());
return this->data()[this->size() - 1];
}
template <std::size_t N>
[[nodiscard]] constexpr span<T, N> first() const {
static_assert(N != dynamic_size && N <= Size);
return {this->data(), N};
}
template <std::size_t N>
[[nodiscard]] constexpr span<T, N> last() const {
static_assert(N != dynamic_size && N <= Size);
return {this->data() + (Size - N), N};
}
template <std::size_t Offset, std::size_t N = dynamic_size>
[[nodiscard]] constexpr auto subspan() const -> detail::subspan_type_t<T, Size, Offset, N> {
static_assert(Offset <= Size);
return {this->data() + Offset, (N == dynamic_size) ? this->size() - Offset : N};
}
[[nodiscard]] constexpr span<T, dynamic_size> first(size_type n) const {
assert(n <= this->size());
return {this->data(), n};
}
[[nodiscard]] constexpr span<T, dynamic_size> last(size_type n) const {
return this->subspan(this->size() - n);
}
[[nodiscard]] constexpr span<T, dynamic_size> subspan(size_type offset, size_type n = dynamic_size) const {
if constexpr (Size == dynamic_size) {
assert(offset <= this->size());
if (n == dynamic_size) {
return {this->data() + offset, this->size() - offset};
}
assert(n <= this->size());
assert(offset + n <= this->size());
return {this->data() + offset, n};
} else {
return span<T, dynamic_size>{*this}.subspan(offset, n);
}
}
};
template <typename T, std::size_t LhsSize, std::size_t RhsSize>
[[nodiscard]] constexpr bool operator==(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename T, std::size_t LhsSize, std::size_t RhsSize>
[[nodiscard]] constexpr bool operator!=(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
return !(lhs == rhs);
}
template <typename T, std::size_t LhsSize, std::size_t RhsSize>
[[nodiscard]] constexpr bool operator<(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename T, std::size_t LhsSize, std::size_t RhsSize>
[[nodiscard]] constexpr bool operator<=(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
return !(lhs > rhs);
}
template <typename T, std::size_t LhsSize, std::size_t RhsSize>
[[nodiscard]] constexpr bool operator>(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
return rhs < lhs;
}
template <typename T, std::size_t LhsSize, std::size_t RhsSize>
[[nodiscard]] constexpr bool operator>=(span<T, LhsSize> lhs, span<T, RhsSize> rhs) {
return !(lhs < rhs);
}
template <typename Container>
span(Container&) -> span<typename Container::value_type>;
template <typename Container>
span(Container const&) -> span<typename Container::value_type const>;
template <typename T, std::size_t N>
span(T (&)[N]) -> span<T, N>;
template <typename T, std::size_t N>
span(std::array<T, N>&) -> span<T, N>;
template <typename T, std::size_t N>
span(std::array<T, N> const&) -> span<T const, N>;
template <typename T, typename Dummy>
span(T, Dummy &&) -> span<std::remove_reference_t<decltype(std::declval<T>()[0])>>;
} // namespace asic
#endif // ASIC_SPAN_HPP
import numpy as np
import pytest
from b_asic import (
SFG,
Addition,
Butterfly,
Constant,
FastSimulation,
Output,
Subtraction,
)
class TestRunFor:
def test_with_lambdas_as_input(self, sfg_two_inputs_two_outputs):
simulation = FastSimulation(
sfg_two_inputs_two_outputs, [lambda n: n + 3, lambda n: 1 + n * 2]
)
output = simulation.run_for(101, save_results=True)
assert output[0] == 304
assert output[1] == 505
assert simulation.results["0"][100] == 304
assert simulation.results["1"][100] == 505
assert simulation.results["in1"][0] == 3
assert simulation.results["in2"][0] == 1
assert simulation.results["add1"][0] == 4
assert simulation.results["add2"][0] == 5
assert simulation.results["0"][0] == 4
assert simulation.results["1"][0] == 5
assert simulation.results["in1"][1] == 4
assert simulation.results["in2"][1] == 3
assert simulation.results["add1"][1] == 7
assert simulation.results["add2"][1] == 10
assert simulation.results["0"][1] == 7
assert simulation.results["1"][1] == 10
assert simulation.results["in1"][2] == 5
assert simulation.results["in2"][2] == 5
assert simulation.results["add1"][2] == 10
assert simulation.results["add2"][2] == 15
assert simulation.results["0"][2] == 10
assert simulation.results["1"][2] == 15
assert simulation.results["in1"][3] == 6
assert simulation.results["in2"][3] == 7
assert simulation.results["add1"][3] == 13
assert simulation.results["add2"][3] == 20
assert simulation.results["0"][3] == 13
assert simulation.results["1"][3] == 20
def test_with_numpy_arrays_as_input(self, sfg_two_inputs_two_outputs):
input0 = np.array([5, 9, 25, -5, 7])
input1 = np.array([7, 3, 3, 54, 2])
simulation = FastSimulation(
sfg_two_inputs_two_outputs, [input0, input1]
)
output = simulation.run_for(5, save_results=True)
assert output[0] == 9
assert output[1] == 11
assert isinstance(simulation.results["in1"], np.ndarray)
assert isinstance(simulation.results["in2"], np.ndarray)
assert isinstance(simulation.results["add1"], np.ndarray)
assert isinstance(simulation.results["add2"], np.ndarray)
assert isinstance(simulation.results["0"], np.ndarray)
assert isinstance(simulation.results["1"], np.ndarray)
assert simulation.results["in1"][0] == 5
assert simulation.results["in2"][0] == 7
assert simulation.results["add1"][0] == 12
assert simulation.results["add2"][0] == 19
assert simulation.results["0"][0] == 12
assert simulation.results["1"][0] == 19
assert simulation.results["in1"][1] == 9
assert simulation.results["in2"][1] == 3
assert simulation.results["add1"][1] == 12
assert simulation.results["add2"][1] == 15
assert simulation.results["0"][1] == 12
assert simulation.results["1"][1] == 15
assert simulation.results["in1"][2] == 25
assert simulation.results["in2"][2] == 3
assert simulation.results["add1"][2] == 28
assert simulation.results["add2"][2] == 31
assert simulation.results["0"][2] == 28
assert simulation.results["1"][2] == 31
assert simulation.results["in1"][3] == -5
assert simulation.results["in2"][3] == 54
assert simulation.results["add1"][3] == 49
assert simulation.results["add2"][3] == 103
assert simulation.results["0"][3] == 49
assert simulation.results["1"][3] == 103
assert simulation.results["0"][4] == 9
assert simulation.results["1"][4] == 11
def test_with_numpy_array_overflow(self, sfg_two_inputs_two_outputs):
input0 = np.array([5, 9, 25, -5, 7])
input1 = np.array([7, 3, 3, 54, 2])
simulation = FastSimulation(
sfg_two_inputs_two_outputs, [input0, input1]
)
simulation.run_for(5)
with pytest.raises(IndexError):
simulation.step()
def test_run_whole_numpy_array(self, sfg_two_inputs_two_outputs):
input0 = np.array([5, 9, 25, -5, 7])
input1 = np.array([7, 3, 3, 54, 2])
simulation = FastSimulation(
sfg_two_inputs_two_outputs, [input0, input1]
)
simulation.run()
assert len(simulation.results["0"]) == 5
assert len(simulation.results["1"]) == 5
with pytest.raises(IndexError):
simulation.step()
def test_delay(self, sfg_delay):
simulation = FastSimulation(sfg_delay)
simulation.set_input(0, [5, -2, 25, -6, 7, 0])
simulation.run_for(6, save_results=True)
assert simulation.results["0"][0] == 0
assert simulation.results["0"][1] == 5
assert simulation.results["0"][2] == -2
assert simulation.results["0"][3] == 25
assert simulation.results["0"][4] == -6
assert simulation.results["0"][5] == 7
def test_find_result_key(self, precedence_sfg_delays):
sim = FastSimulation(
precedence_sfg_delays,
[[0, 4, 542, 42, 31.314, 534.123, -453415, 5431]],
)
sim.run()
assert (
sim.results[
precedence_sfg_delays.find_result_keys_by_name("ADD2")[0]
][4]
== 31220
)
assert (
sim.results[
precedence_sfg_delays.find_result_keys_by_name("A1")[0]
][2]
== 80
)
class TestRun:
def test_save_results(self, sfg_two_inputs_two_outputs):
simulation = FastSimulation(sfg_two_inputs_two_outputs, [2, 3])
assert not simulation.results
simulation.run_for(10, save_results=False)
assert not simulation.results
simulation.run_for(10)
assert len(simulation.results["0"]) == 10
assert len(simulation.results["1"]) == 10
simulation.run_for(10, save_results=True)
assert len(simulation.results["0"]) == 20
assert len(simulation.results["1"]) == 20
simulation.run_for(10, save_results=False)
assert len(simulation.results["0"]) == 20
assert len(simulation.results["1"]) == 20
simulation.run_for(13, save_results=True)
assert len(simulation.results["0"]) == 33
assert len(simulation.results["1"]) == 33
simulation.step(save_results=False)
assert len(simulation.results["0"]) == 33
assert len(simulation.results["1"]) == 33
simulation.step()
assert len(simulation.results["0"]) == 34
assert len(simulation.results["1"]) == 34
simulation.clear_results()
assert not simulation.results
def test_nested(self, sfg_nested):
input0 = np.array([5, 9])
input1 = np.array([7, 3])
simulation = FastSimulation(sfg_nested, [input0, input1])
output0 = simulation.step()
output1 = simulation.step()
assert output0[0] == 11405
assert output1[0] == 4221
def test_accumulator(self, sfg_accumulator):
data_in = np.array([5, -2, 25, -6, 7, 0])
reset = np.array([0, 0, 0, 1, 0, 0])
simulation = FastSimulation(sfg_accumulator, [data_in, reset])
output0 = simulation.step()
output1 = simulation.step()
output2 = simulation.step()
output3 = simulation.step()
output4 = simulation.step()
output5 = simulation.step()
assert output0[0] == 0
assert output1[0] == 5
assert output2[0] == 3
assert output3[0] == 28
assert output4[0] == 0
assert output5[0] == 7
def test_simple_accumulator(self, sfg_simple_accumulator):
data_in = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
simulation = FastSimulation(sfg_simple_accumulator, [data_in])
simulation.run()
assert list(simulation.results["0"]) == [
0,
1,
3,
6,
10,
15,
21,
28,
36,
45,
]
def test_simple_filter(self, sfg_simple_filter):
input0 = np.array([1, 2, 3, 4, 5])
simulation = FastSimulation(sfg_simple_filter, [input0])
simulation.run_for(len(input0), save_results=True)
assert all(
simulation.results["0"] == np.array([0, 1.0, 2.5, 4.25, 6.125])
)
def test_custom_operation(self, sfg_custom_operation):
simulation = FastSimulation(sfg_custom_operation, [lambda n: n + 1])
simulation.run_for(5)
assert all(simulation.results["0"] == np.array([2, 4, 6, 8, 10]))
assert all(simulation.results["1"] == np.array([2, 4, 8, 16, 32]))
class TestLarge:
def test_1k_additions(self):
prev_op = Addition(Constant(1), Constant(1))
for _ in range(999):
prev_op = Addition(prev_op, Constant(2))
sfg = SFG(outputs=[Output(prev_op)])
simulation = FastSimulation(sfg, [])
assert simulation.step()[0] == 2000
def test_1k_subtractions(self):
prev_op = Subtraction(Constant(0), Constant(2))
for _ in range(999):
prev_op = Subtraction(prev_op, Constant(2))
sfg = SFG(outputs=[Output(prev_op)])
simulation = FastSimulation(sfg, [])
assert simulation.step()[0] == -2000
def test_1k_butterfly(self):
prev_op_add = Addition(Constant(1), Constant(1))
prev_op_sub = Subtraction(Constant(-1), Constant(1))
for _ in range(499):
prev_op_add = Addition(prev_op_add, Constant(2))
for _ in range(499):
prev_op_sub = Subtraction(prev_op_sub, Constant(2))
butterfly = Butterfly(prev_op_add, prev_op_sub)
sfg = SFG(
outputs=[Output(butterfly.output(0)), Output(butterfly.output(1))]
)
simulation = FastSimulation(sfg, [])
assert list(simulation.step()) == [0, 2000]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment