diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b1a11f9586..ecbd7eea6a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -12,6 +12,7 @@ jobs: name: Tests runs-on: ubuntu-18.04 strategy: + fail-fast: false matrix: cxx: [clang++, g++] test-set: diff --git a/.gitignore b/.gitignore index e8e2e5a013..d4668f7843 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ *.wasm *.wast *-bak.cc +*-bak.cpp *.dat *.exe *tmp.* @@ -45,7 +46,7 @@ demos/Avida/Avida demos/Avida/web/Avida.js demos/Emphatic/Emphatic demos/Emphatic/examples/ConceptTest -demos/Emphatic/examples/ConceptTest.cc +demos/Emphatic/examples/ConceptTest.cpp demos/MABE/examples/NK demos/NK.bak/ demos/NK/NK @@ -69,6 +70,7 @@ demos/NK/Makefile demos/NK/source demos/NK/source/native demos/NK/source/NKWorld.h +demos/NK/source/NKWorld.hpp demos/NK/source/web demos/NK/web/jquery-1.11.2.min.js demos/NK/web/NK.asm.js @@ -77,12 +79,14 @@ demos/NK/web/NK.js.mem doc/doxygen/ examples/*/* +!examples/*/*.cc !examples/*/*.cpp +!examples/*/*.h !examples/*/*.hpp !examples/*/*.html !examples/*/images !examples/*/Makefile -examples/timing/short_strings.html +!examples/timing/BENCHMARKS tests/*.csv tests/StatsConfig.cfg diff --git a/Dockerfile b/Dockerfile index 7b3ae5f1ea..a699869bec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Pull base image. -FROM ubuntu:18.04 +FROM ubuntu:bionic-20210416 COPY . /opt/Empirical @@ -21,7 +21,16 @@ RUN \ # Install apt packages # xvfb nonsense adapted from https://github.com/samgiles/docker-xvfb # remove -backports, -updates, -proposed, -security repositories +# looks like we have to grab libxxhash0 from -updates now RUN \ + apt-get update -y \ + && \ + apt-get install --no-install-recommends libxxhash0 \ + && \ + apt-get clean \ + && \ + rm -rf /var/lib/apt/lists/* \ + && \ find /etc/apt -type f -name '*.list' -exec sed -i 's/\(^deb.*-backports.*\)/#\1/; s/\(^deb.*-updates.*\)/#\1/; s/\(^deb.*-proposed.*\)/#\1/; s/\(^deb.*-security.*\)/#\1/' {} + \ && \ apt-get update -y \ diff --git a/demos/EasyChair/ProcessReviews.cpp b/demos/EasyChair/ProcessReviews.cpp new file mode 100644 index 0000000000..d89bd36c2b --- /dev/null +++ b/demos/EasyChair/ProcessReviews.cpp @@ -0,0 +1,324 @@ +#include +#include +#include + +#include "../../include/emp/base/vector.hpp" +#include "../../include/emp/io/File.hpp" +#include "../../include/emp/tools/string_utils.hpp" + + +/////////////////////// +// ReviewInfo +/////////////////////// + +struct ReviewInfo +{ + bool is_meta = false; + std::string reviewer_name = ""; + + // scores (only overall used for meta-reviews) + int overall = 0; + int novelty = 0; + int writing = 0; + int lit_review = 0; + int methods = 0; + int relevance = 0; + int quality = 0; + int confidence = 0; + + void Write(std::ostream & os=std::cout) const { + if (is_meta) { + os << "METAREVIEW by " << reviewer_name << ": "; + if (overall == -1) os << "reject\n"; + if (overall == 0) os << "UNDECIDED\n"; + else os << "accept!\n"; + } + else { + os << "REVIEW by " << reviewer_name << ":\n"; + os << " Overall evaluation: " << overall << std::endl; + os << " Novelty/Originality: " << novelty << std::endl; + os << " Writing Clarity: " << writing << std::endl; + os << " Thoroughness of Literature Review: " << lit_review << std::endl; + os << " Thoroughness of Methods: " << methods << std::endl; + os << " Relevance to Artificial Life Conference: " << relevance << std::endl; + os << " Overall Quality of Work: " << quality << std::endl; + os << " Reviewer's confidence: " << confidence << std::endl; + } + } + + void WriteCSV(std::ostream & os=std::cout) const { + os << "\"" << reviewer_name << "\"," << overall; + if (!is_meta) { + os << "," << novelty + << "," << writing + << "," << lit_review + << "," << methods + << "," << relevance + << "," << quality + << "," << confidence; + } + } + + static void WriteCSVMetaHeaders(std::ostream & os=std::cout) { + os << "Metareviewer,Recommendation"; + } + + static void WriteCSVHeaders(std::ostream & os=std::cout) { + os << "Reviewer,Overall" + << ",Novelty" + << ",Writing" + << ",Lit Review" + << ",Methods" + << ",Eelevance" + << ",Quality" + << ",Confidence"; + } +}; + + +/////////////////////// +// PaperInfo +/////////////////////// + +struct PaperInfo +{ + int id = -1; + std::string title = ""; + emp::vector authors; + ReviewInfo meta_review; + emp::vector reviews; + std::string session = ""; + std::string presentation = ""; // Type of presentation requested. + size_t length = 0; + + void SetAuthors(std::string in_authors) { + authors = emp::slice(in_authors, ','); + + // Check to see if we need to split the last one. + size_t split_pos = authors.back().find(" and "); + if (split_pos != std::string::npos) { + std::string last_author = authors.back().substr(split_pos+5); + authors.back().resize(split_pos); + authors.push_back(last_author); + } + + // Remove all spaces at the beginning and end of author's names. + for (std::string & author : authors) emp::justify(author); + } + + std::string GetAuthors() const { + std::stringstream ss; + ss << authors[0]; + for (size_t i = 1; i < authors.size(); i++) { + ss << ", " << authors[i]; + } + return ss.str(); + } + + void Write(std::ostream & os=std::cout) const { + os << "PAPER ID: " << id << std::endl + << "AUTHORS: " << GetAuthors() << std::endl + << "TITLE: " << title << std::endl; + meta_review.Write(os); + for (const ReviewInfo & review : reviews) { + review.Write(os); + } + os << std::endl; + } + + void WriteCSV(std::ostream & os=std::cout) const { + // One line for each review. + for (const ReviewInfo & review : reviews) { + os << id << ",\"" + << GetAuthors() << "\",\"" + << title << "\"," + << length << ",\"" + << presentation << "\",\"" + << session << "\","; + meta_review.WriteCSV(os); + os << ","; + review.WriteCSV(os); + os << "\n"; + } + } + + void WriteCSVHeaders(std::ostream & os=std::cout) const { + os << "Paper ID,Authors,Title,Length,Presentation,Session,"; + ReviewInfo::WriteCSVMetaHeaders(os); + os << ","; + ReviewInfo::WriteCSVHeaders(os); + os << std::endl; + } +}; + + +/////////////////////// +// PaperSet +/////////////////////// + +struct PaperSet { + emp::vector papers; + size_t cur_line = 0; // File line being processed. + int cur_id = -1; // Current paper being setup. + + PaperSet(const std::string & review_filename, const std::string & catagory_filename) + { + ProcessReviewFile(review_filename); + ProcessCatagoryFile(catagory_filename); + } + + // For the moment, summaries are not used - just fast-forward. + void ProcessSummary(const emp::File & file) { + // Summary mode ends with an empty line. + while (file[cur_line] != "") cur_line++; + } + + // Process details about a meta-review + void ProcessMeta(const emp::File & file) { + const std::string & line = file[cur_line++]; + ReviewInfo & info = papers[cur_id].meta_review; + info.is_meta = true; + info.reviewer_name = line.substr(23,line.size()-34); + + // Collect recommendation result (-1 = decline, 0 = undecided, 1 = accept) + const std::string & result = file[cur_line++].substr(16); + if (result == "accept") info.overall = 1; + else if (result == "reject") info.overall = -1; + else info.overall = 0; + + // Meta mode ends with an empty line. + while (file[cur_line] != "") cur_line++; + } + + bool CheckRating(const std::string & line, const std::string & name, int & value) { + if (emp::has_prefix(line, name)) { + size_t pos = name.size() + 1; + value = emp::from_string(emp::string_get_word(line, pos)); + return true; + } + return false; + } + + // Process details about a meta-review + void ProcessReview(const emp::File & file) { + const std::string & line = file[cur_line++]; + papers[cur_id].reviews.push_back(ReviewInfo{}); + ReviewInfo & info = papers[cur_id].reviews.back(); + info.is_meta = false; + info.reviewer_name = line.substr(21,line.size()-32); + + // Reviews end with an empty line. + while (file[cur_line] != "") { + CheckRating(file[cur_line], "Overall evaluation:", info.overall); + CheckRating(file[cur_line], "Novelty/Originality:", info.novelty); + CheckRating(file[cur_line], "Writing Clarity:", info.writing); + CheckRating(file[cur_line], "Thoroughness of Literature Review:", info.lit_review); + CheckRating(file[cur_line], "Thoroughness of Methods:", info.methods); + CheckRating(file[cur_line], "Relevance to Artificial Life Conference:", info.relevance); + CheckRating(file[cur_line], "Overall Quality of Work:", info.quality); + CheckRating(file[cur_line], "Reviewer's confidence:", info.confidence); + + cur_line++; + } + } + + void ProcessReviewFile(const std::string & filename) { + emp::File file(filename); + + // Scan through the file, loading each review. + for (cur_line = 0; cur_line < file.size(); ++cur_line) { + std::string line = file[cur_line]; + + // New paper? + if (emp::has_prefix(line, "*********************** PAPER")) { + std::string id_string = emp::string_get_word(line, 30); + cur_id = emp::from_string(id_string); + if (papers.size() <= (size_t) cur_id) papers.resize(cur_id+1); + papers[cur_id].id = cur_id; + continue; + } + + // If we made it this far, we need to have an id. + emp_assert(cur_id >= 0); + + if (emp::has_prefix(line, "AUTHORS:")) { + emp::string_pop_word(line); + papers[cur_id].SetAuthors(line); + continue; + } + + if (emp::has_prefix(line, "TITLE:")) { + emp::string_pop_word(line); + papers[cur_id].title = line; + emp::justify(papers[cur_id].title); + continue; + } + + // Is this a summary? + if (line == "================== SUMMARY OF REVIEWS =================") { + ProcessSummary(file); + continue; + } + + // Is this a metareview? + if (emp::has_prefix(line, "++++++++++ METAREVIEW")) { + ProcessMeta(file); + continue; + } + + // Is this a regular review? + if (emp::has_prefix(line, "++++++++++ REVIEW")) { + ProcessReview(file); + continue; + } + } + } + + void ProcessCatagoryFile(const std::string & filename) { + emp::File file(filename); + + // Skip the first line; process the rest. + for (size_t i=1; i < file.size(); i++) { + auto row = file.ViewRowSlices(i); + size_t id = emp::from_string(row[0]); + if (row.size() > 4) papers[id].length = emp::from_string(row[3]); + if (row.size() > 4) papers[id].presentation = row[4]; + if (row.size() > 5) papers[id].session = row[5]; + } + } + + void Print() { + // Print the results... + for (const auto & paper : papers) { + if (paper.id < 0) continue; + paper.Write(); + } + } + + void PrintCVS() { + // Find the first review and use it to print the headers. + size_t first = 0; + while (papers[first].id < 0) first++; + papers[first].WriteCSVHeaders(); + + // Now print all of the actual reviews. + for (const auto & paper : papers) { + if (paper.id < 0) continue; + paper.WriteCSV(); + } + } + +}; + +int main(int argc, char * argv[]) +{ + if (argc != 3) { + std::cerr << "Format: " << argv[0] << " [review filename] [catagory filename]\n"; + exit(1); + } + + std::string review_filename(argv[1]); + std::string catagory_filename(argv[2]); + PaperSet ps(review_filename, catagory_filename); + ps.PrintCVS(); +} diff --git a/demos/utils/levelize/README.md b/demos/utils/levelize/README.md new file mode 100644 index 0000000000..b09e9b988b --- /dev/null +++ b/demos/utils/levelize/README.md @@ -0,0 +1,7 @@ +# Levelization tool for software packages + +This utility takes in a series of filenames and then tracks which of those files +include each other to build a levelization map. + +The specific algorithm will load each file, search for lines with a #include, and +then grabs the final filename on those lines (after removing any comments). diff --git a/demos/utils/levelize/levelize.cpp b/demos/utils/levelize/levelize.cpp new file mode 100644 index 0000000000..2f873a912b --- /dev/null +++ b/demos/utils/levelize/levelize.cpp @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include + +#include "../../../include/emp/base/vector.hpp" +#include "../../../include/emp/io/File.hpp" +#include "../../../include/emp/tools/string_utils.hpp" + +using level_t = uint32_t; + +struct FileInfo { + std::string filename; + std::string path; + std::set depends; // Which OTHER files does this one depend on? + + static constexpr level_t NO_LEVEL = (level_t) -1; + level_t level = NO_LEVEL; +}; + +int main(int argc, char * argv[]) +{ + if (argc == 1) { + std::cerr << "No files listed.\nPlease run `" << argv[0] << " --help` for more info.\n"; + exit(0); + } + + // Load in all of the files that we are working with. + const size_t num_files = argc - 1; + emp::vector files(num_files); + for (size_t i = 0; i < num_files; i++) files[i] = argv[i+1]; + + // Check if we're just supposed to print the help info. + if (files[0] == "--help") { + std::cerr << "Format: " << argv[0] << " [args] {filename} [filenames...]\n" + << "Available args:\n" + << " -v : verbose output\n"; + exit(0); + } + + std::cerr << num_files << " files found. Processing!" << std::endl; + + bool verbose = false; + + // Simplify to just the filenames (remove paths) + std::map file_map; + emp::vector filenames; + for (std::string & file : files) { + if (file == "-v") { + verbose = true; + continue; + } + emp::vector dir_struct = emp::view_slices(file, '/'); + std::string filename(dir_struct.back()); + file_map[filename].filename = filename; + file_map[filename].path = file; + filenames.push_back(filename); + } + + // For each file, scan for its dependencies. + for (auto & [filename, info] : file_map) { + if (verbose) { + std::cerr << "Scanning '" << filename << "' found at: " + << info.path << std::endl; + } + + emp::File file(info.path); + file.KeepIfContains("#include"); // Only scan through include lines. + file.RemoveIfContains("third-party"); // Ignore includes from third-party directory (may duplicate names) + + // Now test which OTHER filenames it is including. Search for the filename with + // a " or / in front of it (to make sure it's not part of another name) + int include_count = 0; + for (const std::string & filename : filenames) { + if (file.Contains(emp::to_string("\"", filename)) || + file.Contains(emp::to_string("/", filename)) ) { + info.depends.insert(filename); + include_count++; + } + } + if (verbose) { + std::cerr << "...has " << include_count << " includes." << std::endl; + } + } + + // Now that we know dependences, figure out levels! + level_t max_level = 0; + bool progress = true; + while (progress) { + progress = false; + + if (verbose) { + std::cerr << "Processing!" << std::endl; + } + + // Loop through each file to see if we can determine its level. + for (auto & [filename, info] : file_map) { + if (info.level != FileInfo::NO_LEVEL) continue; // Already has a level! + + // See if we can determine a level for this file. + level_t new_level = 0; + for (const std::string & depend_name : info.depends) { + level_t test_level = file_map[depend_name].level; + + // If a dependency doesn't have a level yet, stop working on this one. + if (test_level == FileInfo::NO_LEVEL) { + new_level = FileInfo::NO_LEVEL; + break; + } + + // Otherwise see if we need to update our new_level for this file. + if (test_level >= new_level) new_level = test_level + 1; + } + + // If we have a level for this file now, use it an indicate progress! + if (new_level != FileInfo::NO_LEVEL) { + if (verbose) { + std::cerr << "..." << info.filename << " assigned to level " << new_level << std::endl; + } + + info.level = new_level; + + if (new_level > max_level) max_level = new_level; + progress = true; + } + } + } + + // List out the files and their levels. + for (level_t level = 0; level <= max_level; level++) { + std::cout << "============ LEVEL " << level << " ============\n"; + for (auto [filename, info] : file_map) { + if (info.level != level) continue; + std::cout << filename << " " << " (" << info.path << ")\n"; + if (level == 0) continue; + std::cout << " :"; + for (const std::string & name : info.depends) { + std::cout << " " << name << "(" << file_map[name].level << ")"; + } + std::cout << std::endl; + } + } + + // Identify any files that we were NOT able to handle, if any. + int unknown_count = 0; + for (auto & [filename, info] : file_map) { + if (info.level != FileInfo::NO_LEVEL) continue; // Has a level! + + // Only print a header for this section if it has entries. + if (unknown_count++ == 0) { + std::cout << "\n============ UNKNOWN LEVEL! ============\n"; + } + + std::cout << filename << " " << " (" << info.path << ")\n"; + std::cout << " :"; + for (const std::string & name : info.depends) { + std::string level = "Unknown"; + if (file_map[name].level != FileInfo::NO_LEVEL) level = emp::to_string(file_map[name].level); + std::cout << " " << name << "(" << level << ")"; + } + std::cout << std::endl; + } + if (verbose) { + std::cerr << "Number of files with unknown levels: " << unknown_count << std::endl; + } + +} diff --git a/examples/base/Makefile b/examples/base/Makefile index 00875eedab..0540edf6de 100644 --- a/examples/base/Makefile +++ b/examples/base/Makefile @@ -19,7 +19,7 @@ CFLAGS_web_debug := $(CFLAGS_all) $(OFLAGS_web_debug) --js-library ../../include CFLAGS_web_opt := $(CFLAGS_all) $(OFLAGS_web_opt) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s NO_EXIT_RUNTIME=1 #CFLAGS_web := $(CFLAGS_all) $(OFLAGS_web) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s DISABLE_EXCEPTION_CATCHING=1 -s NO_EXIT_RUNTIME=1 -TARGETS := assert errors macros map Ptr unordered_map vector +TARGETS := assert errors map Ptr unordered_map vector default: native diff --git a/examples/bits/BitVector.cpp b/examples/bits/BitVector.cpp index 6d3d49af65..1339385dbf 100644 --- a/examples/bits/BitVector.cpp +++ b/examples/bits/BitVector.cpp @@ -53,7 +53,7 @@ int main() bv_set.insert(set2); emp::BitVector bv(10); - std::cout << bv.Hash() << " (initial, 10 bits)" << std::endl; + std::cout << bv.Hash() << " (initial Hash, 10 bits)" << std::endl; bv[3] = true; std::cout << bv.Hash() << " (bit 3 set to true)" << std::endl; bv.Resize(9); diff --git a/examples/datastructs/Makefile b/examples/datastructs/Makefile index cd56bb9e5d..a4ca0afc60 100644 --- a/examples/datastructs/Makefile +++ b/examples/datastructs/Makefile @@ -20,7 +20,7 @@ CFLAGS_web_debug := $(CFLAGS_all) $(OFLAGS_web_debug) --js-library ../../include CFLAGS_web_opt := $(CFLAGS_all) $(OFLAGS_web_opt) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s NO_EXIT_RUNTIME=1 #CFLAGS_web := $(CFLAGS_all) $(OFLAGS_web) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s DISABLE_EXCEPTION_CATCHING=1 -s NO_EXIT_RUNTIME=1 -TARGETS := Cache IndexMap ra_set StringMap TimeQueue tuple_utils TypeMap valsort_map vector_utils +TARGETS := Cache hash_utils IndexMap ra_set StringMap TimeQueue tuple_utils TypeMap valsort_map vector_utils default: native diff --git a/examples/datastructs/StringMap.cpp b/examples/datastructs/StringMap.cpp index 962987ce18..0977867f8d 100644 --- a/examples/datastructs/StringMap.cpp +++ b/examples/datastructs/StringMap.cpp @@ -1,5 +1,5 @@ // This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2018. +// Copyright (C) Michigan State University, 2018-2021. // Released under the MIT Software license; see doc/LICENSE // // @@ -24,17 +24,17 @@ int main() PRINT_VAL( test_map["Fifteen"] ); - test_map[EMP_STRING("Alpha")] = 1; - test_map[EMP_STRING("Beta")] = 2; - test_map[EMP_STRING("Gamma")] = 3; + // test_map[EMP_STRING("Alpha")] = 1; + // test_map[EMP_STRING("Beta")] = 2; + // test_map[EMP_STRING("Gamma")] = 3; - PRINT_VAL(test_map[EMP_STRING("Beta")]); + // PRINT_VAL(test_map[EMP_STRING("Beta")]); PRINT_VAL(test_map["Beta"]); - test_map.CTGet("Alpha") = 5; - test_map.CTGet("Beta") = 6; - test_map.CTGet("Gamma") = 7; + // test_map.CTGet("Alpha") = 5; + // test_map.CTGet("Beta") = 6; + // test_map.CTGet("Gamma") = 7; - PRINT_VAL(test_map[EMP_STRING("Gamma")]); + // PRINT_VAL(test_map[EMP_STRING("Gamma")]); PRINT_VAL(test_map["Gamma"]); } diff --git a/examples/datastructs/hash_utils.cpp b/examples/datastructs/hash_utils.cpp new file mode 100644 index 0000000000..8ff5975526 --- /dev/null +++ b/examples/datastructs/hash_utils.cpp @@ -0,0 +1,26 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2021. +// Released under the MIT Software license; see doc/LICENSE +// +// +// Some examples code for using hash_utils.h + +#include +#include + +#include "emp/datastructs/hash_utils.hpp" + +int main() +{ + // Test CombineHash() + std::cout << "\nHash results...:\n"; + std::cout << "hash(2) = " << std::hash()(2) << std::endl + << "hash(3) = " << std::hash()(3) << std::endl + << "hash(4) = " << std::hash()(4) << std::endl + << "CombineHash(4) = " << emp::CombineHash(4) << std::endl + << "CombineHash(2,3) = " << emp::CombineHash(2,3) << std::endl + << "CombineHash(2,3) = " << emp::CombineHash(2,3) << std::endl + << "CombineHash(3,2) = " << emp::CombineHash(3,2) << std::endl + << "CombineHash(3,4) = " << emp::CombineHash(3,4) << std::endl + << "CombineHash(2,3,4) = " << emp::CombineHash(2,3,4) << std::endl; +} diff --git a/examples/datastructs/tuple_utils.cpp b/examples/datastructs/tuple_utils.cpp index 4e33f29bfd..faecea32f6 100644 --- a/examples/datastructs/tuple_utils.cpp +++ b/examples/datastructs/tuple_utils.cpp @@ -1,5 +1,5 @@ // This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2016-2018. +// Copyright (C) Michigan State University, 2016-2021. // Released under the MIT Software license; see doc/LICENSE // // @@ -24,20 +24,6 @@ int main() std::tuple tup = std::make_tuple(1,2,3); test_map[tup] = 1.5; - // Use ApplyTuple - std::cout << "\nApplyTuple results...:\n"; - int x = 10; - int y = 13; - int z = 22; - auto test_tup = std::make_tuple(x,y,z); - std::cout << "Sum3(" << x << "," << y << "," << z << ") = " - << emp::ApplyTuple(Sum3, test_tup) << std::endl; - std::cout << "Prod3(" << x << "," << y << "," << z << ") = " - << emp::ApplyTuple([](int x, int y, int z){ return x*y*z; }, test_tup) << std::endl; - - std::cout << "CombineHash(" << x << "," << y << "," << z << ") = " - << emp::ApplyTuple(emp::CombineHash, test_tup) << std::endl; - std::cout << "\nPrintTwice with TupleIterate:\n"; emp::TupleIterate(tup, PrintTwice); diff --git a/examples/hardware/LinearCode.cpp b/examples/hardware/LinearCode.cpp index 27d0bf97d2..b472fc2a6c 100644 --- a/examples/hardware/LinearCode.cpp +++ b/examples/hardware/LinearCode.cpp @@ -3,8 +3,8 @@ * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2017 * - * @file AvidaGP.h - * @brief This is example code for using LinearCode.h + * @file LinearCode.cpp + * @brief This is example code for using LinearCode.hpp */ #include diff --git a/examples/meta/Makefile b/examples/meta/Makefile index ab9620e1c5..8b534f319b 100644 --- a/examples/meta/Makefile +++ b/examples/meta/Makefile @@ -20,7 +20,7 @@ CFLAGS_web_debug := $(CFLAGS_all) $(OFLAGS_web_debug) --js-library ../../include CFLAGS_web_opt := $(CFLAGS_all) $(OFLAGS_web_opt) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s NO_EXIT_RUNTIME=1 #CFLAGS_web := $(CFLAGS_all) $(OFLAGS_web) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s DISABLE_EXCEPTION_CATCHING=1 -s NO_EXIT_RUNTIME=1 -TARGETS := ConceptWrapper meta reflection StringType TypePack ValPack +TARGETS := ConceptWrapper macros meta reflection TypePack ValPack default: native diff --git a/examples/meta/StringType.cpp b/examples/meta/StringType.cpp deleted file mode 100644 index 03f483c1b3..0000000000 --- a/examples/meta/StringType.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2018. -// Released under the MIT Software license; see doc/LICENSE -// -// -// Some example code for using IntPack - -#include -#include -#include - -#include "emp/meta/StringType.hpp" - -int main() -{ - EMP_TEXT_TYPE(test_t, "This is a test!"); - std::cout << test_t::ToString() << std::endl; - - - std::cout << EMP_TEXT_HASH("Test Hash!") << std::endl; - std::cout << EMP_TEXT_HASH("Test Hash2!") << std::endl; - std::cout << EMP_TEXT_HASH("Test Hash3!") << std::endl; - std::cout << EMP_TEXT_HASH("Test Hash!") << std::endl; - std::cout << EMP_TEXT_HASH("Test Hash3!") << std::endl; -} diff --git a/examples/base/macros.cpp b/examples/meta/macros.cpp similarity index 96% rename from examples/base/macros.cpp rename to examples/meta/macros.cpp index 7ebdaab0bf..3a16231561 100644 --- a/examples/base/macros.cpp +++ b/examples/meta/macros.cpp @@ -1,11 +1,11 @@ // This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2016-2017. +// Copyright (C) Michigan State University, 2016-2021. // Released under the MIT Software license; see doc/LICENSE #include +#include "emp/meta/macros.hpp" #include "emp/meta/reflection.hpp" -#include "emp/base/macros.hpp" #define SHOW_MACRO(...) #__VA_ARGS__ " = " EMP_STRINGIFY( __VA_ARGS__ ) diff --git a/examples/meta/meta.cpp b/examples/meta/meta.cpp index cd9511608d..0e5999470d 100644 --- a/examples/meta/meta.cpp +++ b/examples/meta/meta.cpp @@ -32,18 +32,6 @@ int main() std::cout << map2.b << std::endl; - // Test CombineHash() - std::cout << "\nHash results...:\n"; - std::cout << "hash(2) = " << std::hash()(2) << std::endl - << "hash(3) = " << std::hash()(3) << std::endl - << "hash(4) = " << std::hash()(4) << std::endl - << "CombineHash(4) = " << emp::CombineHash(4) << std::endl - << "CombineHash(2,3) = " << emp::CombineHash(2,3) << std::endl - << "CombineHash(2,3) = " << emp::CombineHash(2,3) << std::endl - << "CombineHash(3,2) = " << emp::CombineHash(3,2) << std::endl - << "CombineHash(3,4) = " << emp::CombineHash(3,4) << std::endl - << "CombineHash(2,3,4) = " << emp::CombineHash(2,3,4) << std::endl; - // Test Math... using math_t = emp::tIntMath<1, 2, 3, 4>; std::cout << "\nMath Tests:\n"; diff --git a/examples/timing/BENCHMARKS/bit_timings.txt b/examples/timing/BENCHMARKS/bit_timings.txt new file mode 100644 index 0000000000..6171befaa1 --- /dev/null +++ b/examples/timing/BENCHMARKS/bit_timings.txt @@ -0,0 +1,70 @@ +=== Timings for 'clear' === + size: 1 count: 20000 BitSet: 0.003249 BitVector: 0.060379 Ratio: 0.0538101 + size: 8 count: 20000 BitSet: 0.002845 BitVector: 0.060451 Ratio: 0.0470629 + size: 31 count: 20000 BitSet: 0.002731 BitVector: 0.062313 Ratio: 0.0438271 + size: 32 count: 20000 BitSet: 0.002934 BitVector: 0.061767 Ratio: 0.0475011 + size: 50 count: 20000 BitSet: 0.002721 BitVector: 0.067965 Ratio: 0.0400353 + size: 63 count: 20000 BitSet: 0.003375 BitVector: 0.069989 Ratio: 0.0482219 + size: 64 count: 20000 BitSet: 0.002796 BitVector: 0.06322 Ratio: 0.0442265 + size: 100 count: 20000 BitSet: 0.006019 BitVector: 0.068559 Ratio: 0.087793 + size: 1000 count: 5120 BitSet: 0.011796 BitVector: 0.02376 Ratio: 0.496465 + size: 10000 count: 512 BitSet: 0.012209 BitVector: 0.01681 Ratio: 0.726294 + size: 100000 count: 51 BitSet: 0.014463 BitVector: 0.016146 Ratio: 0.895764 + size: 1000000 count: 5 BitSet: 0.014226 BitVector: 0.013363 Ratio: 1.06458 + + +=== Timings for 'set_all' === + size: 1 count: 20000 BitSet: 0.00303 BitVector: 0.100021 Ratio: 0.0302936 + size: 8 count: 20000 BitSet: 0.002295 BitVector: 0.091984 Ratio: 0.02495 + size: 31 count: 20000 BitSet: 0.00217 BitVector: 0.085641 Ratio: 0.0253383 + size: 32 count: 20000 BitSet: 0.002419 BitVector: 0.083636 Ratio: 0.028923 + size: 50 count: 20000 BitSet: 0.002115 BitVector: 0.083411 Ratio: 0.0253564 + size: 63 count: 20000 BitSet: 0.002294 BitVector: 0.087604 Ratio: 0.026186 + size: 64 count: 20000 BitSet: 0.002691 BitVector: 0.080239 Ratio: 0.0335373 + size: 100 count: 20000 BitSet: 0.006917 BitVector: 0.089793 Ratio: 0.0770327 + size: 1000 count: 5120 BitSet: 0.033724 BitVector: 0.030493 Ratio: 1.10596 + size: 10000 count: 512 BitSet: 0.01938 BitVector: 0.020851 Ratio: 0.929452 + size: 100000 count: 51 BitSet: 0.017735 BitVector: 0.017994 Ratio: 0.985606 + size: 1000000 count: 5 BitSet: 0.014452 BitVector: 0.012858 Ratio: 1.12397 + +=== Timings for 'randomize' === + size: 1 count: 20000 BitSet: 0.103631 BitVector: 0.140396 Ratio: 0.738134 + size: 8 count: 20000 BitSet: 0.04084 BitVector: 0.13689 Ratio: 0.298342 + size: 31 count: 20000 BitSet: 0.095266 BitVector: 0.10784 Ratio: 0.883401 + size: 32 count: 20000 BitSet: 0.04115 BitVector: 0.109808 Ratio: 0.374745 + size: 50 count: 20000 BitSet: 0.109984 BitVector: 0.164857 Ratio: 0.667148 + size: 63 count: 20000 BitSet: 0.088319 BitVector: 0.126686 Ratio: 0.697149 + size: 64 count: 20000 BitSet: 0.08151 BitVector: 0.076133 Ratio: 1.07063 + size: 100 count: 20000 BitSet: 0.156624 BitVector: 0.182218 Ratio: 0.859542 + size: 1000 count: 5120 BitSet: 0.332355 BitVector: 0.326727 Ratio: 1.01723 + size: 10000 count: 512 BitSet: 0.32555 BitVector: 0.321152 Ratio: 1.01369 + size: 100000 count: 51 BitSet: 0.323101 BitVector: 0.32021 Ratio: 1.00903 + size: 1000000 count: 5 BitSet: 0.315316 BitVector: 0.317433 Ratio: 0.993331 + +=== Timings for 'randomize75' === + size: 1 count: 20000 BitSet: 0.095342 BitVector: 0.085534 Ratio: 1.11467 + size: 8 count: 20000 BitSet: 0.064832 BitVector: 0.14294 Ratio: 0.453561 + size: 31 count: 20000 BitSet: 0.516913 BitVector: 0.633922 Ratio: 0.815421 + size: 32 count: 20000 BitSet: 0.067439 BitVector: 0.099907 Ratio: 0.675018 + size: 50 count: 20000 BitSet: 0.319654 BitVector: 0.309046 Ratio: 1.03432 + size: 63 count: 20000 BitSet: 0.682278 BitVector: 0.678855 Ratio: 1.00504 + size: 64 count: 20000 BitSet: 0.119706 BitVector: 0.122847 Ratio: 0.974432 + size: 100 count: 20000 BitSet: 0.45523 BitVector: 0.509892 Ratio: 0.892797 + size: 1000 count: 5120 BitSet: 0.490299 BitVector: 0.486445 Ratio: 1.00792 + size: 10000 count: 512 BitSet: 0.496028 BitVector: 0.480745 Ratio: 1.03179 + size: 100000 count: 51 BitSet: 0.508294 BitVector: 0.468891 Ratio: 1.08403 + size: 1000000 count: 5 BitSet: 0.465654 BitVector: 0.460608 Ratio: 1.01096 + +=== Timings for 'randomize82' === + size: 1 count: 20000 BitSet: 0.097639 BitVector: 0.053949 Ratio: 1.80984 + size: 8 count: 20000 BitSet: 0.299518 BitVector: 0.318175 Ratio: 0.941362 + size: 31 count: 20000 BitSet: 1.19827 BitVector: 1.25781 Ratio: 0.952669 + size: 32 count: 20000 BitSet: 1.42109 BitVector: 1.31851 Ratio: 1.0778 + size: 50 count: 20000 BitSet: 2.33136 BitVector: 2.09982 Ratio: 1.11027 + size: 63 count: 20000 BitSet: 2.64438 BitVector: 2.55956 Ratio: 1.03314 + size: 64 count: 20000 BitSet: 3.05496 BitVector: 2.70889 Ratio: 1.12775 + size: 100 count: 20000 BitSet: 4.10899 BitVector: 4.43622 Ratio: 0.926236 + size: 1000 count: 5120 BitSet: 10.5527 BitVector: 10.5609 Ratio: 0.999216 + size: 10000 count: 512 BitSet: 10.1709 BitVector: 10.3389 Ratio: 0.983754 + size: 100000 count: 51 BitSet: 9.96224 BitVector: 10.2105 Ratio: 0.975685 + size: 1000000 count: 5 BitSet: 10.5416 BitVector: 10.2855 Ratio: 1.0249 diff --git a/examples/timing/Makefile b/examples/timing/Makefile index 3a31f4b148..c1ae00ec39 100644 --- a/examples/timing/Makefile +++ b/examples/timing/Makefile @@ -20,7 +20,7 @@ CFLAGS_web_debug := $(CFLAGS_all) $(OFLAGS_web_debug) --js-library ../../include CFLAGS_web_opt := $(CFLAGS_all) $(OFLAGS_web_opt) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s NO_EXIT_RUNTIME=1 #CFLAGS_web := $(CFLAGS_all) $(OFLAGS_web) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s DISABLE_EXCEPTION_CATCHING=1 -s NO_EXIT_RUNTIME=1 -TARGETS := IndexMap Othello pointers Random_timings +TARGETS := bit_timings IndexMap Othello pointers Random_timings default: native diff --git a/examples/timing/bit_timings.cpp b/examples/timing/bit_timings.cpp new file mode 100644 index 0000000000..194e17d0c0 --- /dev/null +++ b/examples/timing/bit_timings.cpp @@ -0,0 +1,180 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2020-2021. +// Released under the MIT Software license; see doc/LICENSE +// +// Some code testing the speed of operations on BitSet and BitVector. + +#include // For std::max +#include // For std::clock +#include // For std::setw +#include + +#include "emp/bits/BitVector.hpp" +#include "emp/base/array.hpp" +#include "emp/base/vector.hpp" +#include "emp/bits/BitSet.hpp" +#include "emp/math/Random.hpp" + +#define TEST_SIZES 1, 8, 31, 32, 50, 63, 64, 100, 1000, 10000, 100000, 1000000 + +// How many total bits should we work with? The below represents 80 meg worth per test. +static constexpr size_t TEST_BITS = 5120000; +//static constexpr size_t TEST_BITS = 1000000; +static constexpr size_t TEST_COUNT = 1000; + + +// Return the timing of a function in seconds. +template +double TimeFunction(T && fun) { + std::clock_t start_time = std::clock(); + fun(); + std::clock_t total_time = std::clock() - start_time; + return total_time / (double) CLOCKS_PER_SEC; +} + +// Return the timing of a function in seconds. +template +double MultiTimeFunction(T && fun) { + std::clock_t start_time = std::clock(); + for (size_t i = 0; i < TEST_COUNT; ++i) fun(); + double total_time = (double) (std::clock() - start_time); + return total_time / (double) CLOCKS_PER_SEC; +} + + +using size_timings_t = std::map; // Map bit sizes to the associated time. +using timings_t = std::map; // Map names to all timings. + +template struct SpeedTester_impl { }; + +template +struct SpeedTester_impl : public SpeedTester_impl { + static constexpr size_t CUR_BITS = SIZE1; + static constexpr bool HAS_OTHERS = sizeof...(OTHER_SIZES) > 0; + + // How many bits should we treat each object as? Put a floor of 256 bits. + static constexpr size_t OBJ_BITS = (CUR_BITS > 256) ? CUR_BITS : 256; + + // How many objects should we use? + static constexpr size_t OBJ_COUNT = TEST_BITS / OBJ_BITS; + + emp::array< emp::BitSet, OBJ_COUNT > bs_objs; + emp::array< emp::BitVector, OBJ_COUNT > bv_objs; + + using base_t = SpeedTester_impl; + + template auto GetBitSet(size_t index) { + if constexpr(SIZE_ID == 0) return bs_objs[index]; + else return base_t::template GetBitSet(index); + } + + template auto GetBitVector(size_t index) { + if constexpr(SIZE_ID == 0) return bv_objs[index]; + else return base_t::template GetBitVector(index); + } + + void TestClear(size_timings_t & bs_map, size_timings_t & bv_map) { + std::cout << "Testing 'clear' for size " << SIZE1 << std::endl; + bs_map[SIZE1] = MultiTimeFunction( [this](){ for (auto & x : bs_objs) x.Clear(); } ); + bv_map[SIZE1] = MultiTimeFunction( [this](){ for (auto & x : bv_objs) x.Clear(); } ); + if constexpr (HAS_OTHERS) base_t::TestClear(bs_map, bv_map); + } + + void TestSetAll(size_timings_t & bs_map, size_timings_t & bv_map) { + std::cout << "Testing 'set_all' for size " << SIZE1 << std::endl; + bs_map[SIZE1] = MultiTimeFunction([this](){ for (auto & x : bs_objs) x.SetAll(); }); + bv_map[SIZE1] = MultiTimeFunction([this](){ for (auto & x : bv_objs) x.SetAll(); }); + if constexpr (HAS_OTHERS) base_t::TestSetAll(bs_map, bv_map); + } + + void TestToggleRange(size_timings_t & bs_map, size_timings_t & bv_map) { + std::cout << "Testing 'toggle_range' for size " << SIZE1 << std::endl; + const size_t end_pos = std::max(1, SIZE1 - 1); + bs_map[SIZE1] = MultiTimeFunction([this](){ for (auto & x : bs_objs) x.Toggle(1, end_pos); }); + bv_map[SIZE1] = MultiTimeFunction([this](){ for (auto & x : bv_objs) x.Toggle(1, end_pos); }); + if constexpr (HAS_OTHERS) base_t::TestToggleRange(bs_map, bv_map); + } + + void TestRandomize(size_timings_t & bs_map, size_timings_t & bv_map, emp::Random & random) { + std::cout << "Testing 'randomize' for size " << SIZE1 << std::endl; + bs_map[SIZE1] = MultiTimeFunction([this, &random](){ for (auto & x : bs_objs) x.Randomize(random); }); + bv_map[SIZE1] = MultiTimeFunction([this, &random](){ for (auto & x : bv_objs) x.Randomize(random); }); + if constexpr (HAS_OTHERS) base_t::TestRandomize(bs_map, bv_map, random); + } + + void TestRandomize75(size_timings_t & bs_map, size_timings_t & bv_map, emp::Random & random) { + std::cout << "Testing 'randomize75' for size " << SIZE1 << std::endl; + bs_map[SIZE1] = MultiTimeFunction([this, &random](){ for (auto & x : bs_objs) x.Randomize(random, 0.75); }); + bv_map[SIZE1] = MultiTimeFunction([this, &random](){ for (auto & x : bv_objs) x.Randomize(random, 0.75); }); + if constexpr (HAS_OTHERS) base_t::TestRandomize75(bs_map, bv_map, random); + } + + void TestRandomize82(size_timings_t & bs_map, size_timings_t & bv_map, emp::Random & random) { + std::cout << "Testing 'randomize82' for size " << SIZE1 << std::endl; + bs_map[SIZE1] = MultiTimeFunction([this, &random](){ for (auto & x : bs_objs) x.Randomize(random, 0.82); }); + bv_map[SIZE1] = MultiTimeFunction([this, &random](){ for (auto & x : bv_objs) x.Randomize(random, 0.82); }); + if constexpr (HAS_OTHERS) base_t::TestRandomize82(bs_map, bv_map, random); + } + + SpeedTester_impl() { + for (auto & x : bv_objs) x.resize(SIZE1); + } +}; + +struct SpeedTester { + SpeedTester_impl impl; + + timings_t bs_timings; + timings_t bv_timings; + emp::Random random; + + void PrintResults(timings_t bs_timings, timings_t bv_timings, const std::string & name) { + emp::vector sizes{ TEST_SIZES }; + + std::cout << "=== Timings for '" << name << "' ===\n"; + + for (size_t size : sizes) { + size_t obj_bits = (size > 256) ? size : 256; + size_t obj_count = TEST_BITS / obj_bits; + + std::cout << std::left + << " size: " << std::setw(7) << size + << " count: " << std::setw(7) << obj_count + << " BitSet: " << std::setw(8) << bs_timings[name][size] + << " BitVector: " << std::setw(8) << bv_timings[name][size] + << " Ratio: " << std::setw(8) << (bs_timings[name][size] / bv_timings[name][size]) + << std::endl; + } + } + + void RunTests() { + // Conduct the tests. + impl.TestClear(bs_timings["clear"], bv_timings["clear"]); + impl.TestSetAll(bs_timings["set_all"], bv_timings["set_all"]); + impl.TestToggleRange(bs_timings["toggle_range"], bv_timings["toggle_range"]); + impl.TestRandomize(bs_timings["randomize"], bv_timings["randomize"], random); + impl.TestRandomize75(bs_timings["randomize75"], bv_timings["randomize75"], random); + impl.TestRandomize82(bs_timings["randomize82"], bv_timings["randomize82"], random); + } + + void PrintResults() { + // Print the results. + PrintResults(bs_timings, bv_timings, "clear"); + PrintResults(bs_timings, bv_timings, "set_all"); + PrintResults(bs_timings, bv_timings, "toggle_range"); + PrintResults(bs_timings, bv_timings, "randomize"); + PrintResults(bs_timings, bv_timings, "randomize75"); + PrintResults(bs_timings, bv_timings, "randomize82"); + } +}; + + +int main() +{ + const emp::vector test_sizes = { TEST_SIZES }; + + SpeedTester speed_tester; + + speed_tester.RunTests(); + speed_tester.PrintResults(); +} diff --git a/include/emp/Evolve/NK.hpp b/include/emp/Evolve/NK.hpp index e89d36dbd0..951c33dd71 100644 --- a/include/emp/Evolve/NK.hpp +++ b/include/emp/Evolve/NK.hpp @@ -172,8 +172,8 @@ namespace emp { class NKLandscapeMemo { private: - const size_t N; - const size_t K; + size_t N; + size_t K; mutable emp::vector< emp::memo_function > landscape; emp::vector masks; diff --git a/include/emp/Evolve/SystematicsAnalysis.hpp b/include/emp/Evolve/SystematicsAnalysis.hpp index d6947b350e..2844605809 100644 --- a/include/emp/Evolve/SystematicsAnalysis.hpp +++ b/include/emp/Evolve/SystematicsAnalysis.hpp @@ -10,7 +10,7 @@ #ifndef EMP_EVO_SYSTEMATICS_ANALYSIS_H #define EMP_EVO_SYSTEMATICS_ANALYSIS_H -#include "Systematics.hpp" +#include "../base/Ptr.hpp" // Mutation info functions. Assumes each taxon has a struct containing an unordered map // with keys that are strings indicating types of mutations and keys that are numbers diff --git a/include/emp/Evolve/World.hpp b/include/emp/Evolve/World.hpp index e2745616f5..26da164e3b 100644 --- a/include/emp/Evolve/World.hpp +++ b/include/emp/Evolve/World.hpp @@ -257,13 +257,15 @@ namespace emp { /// How many cells wide is the world? (assumes grids are active.) size_t GetWidth() const { - emp_assert(HasAttribute("PopStruct") && GetAttribute("PopStruct") == "Grid"); + emp_assert(HasAttribute("PopStruct")); + emp_assert(GetAttribute("PopStruct") == "Grid" || GetAttribute("PopStruct") == "3DGrid"); return pop_sizes[0]; } /// How many cells tall is the world? (assumes grids are active.) size_t GetHeight() const { - emp_assert(HasAttribute("PopStruct") && GetAttribute("PopStruct") == "Grid"); + emp_assert(HasAttribute("PopStruct")); + emp_assert(GetAttribute("PopStruct") == "Grid" || GetAttribute("PopStruct") == "3DGrid"); return pop_sizes[1]; } @@ -1098,6 +1100,8 @@ namespace emp { template void World::SetPopStruct_Grid(size_t width, size_t height, bool synchronous_gen) { + emp_assert(width * height >= 2, width, height, "A 2D Grid must have at least 2 cells."); + Resize(width, height); is_synchronous = synchronous_gen; is_space_structured = true; @@ -1120,17 +1124,21 @@ namespace emp { const size_t size_y = pop_sizes[1]; const size_t id = pos.GetIndex(); - // fancy footwork to exclude self (4) from consideration - const int offset = (random_ptr->GetInt(8) * 5) % 9; - const int rand_x = (int) (id%size_x) + offset%3 - 1; - const int rand_y = (int) (id/size_x) + offset/3 - 1; + // Determine x and y for a random neighbor. + int offset = random_ptr->GetInt(8); // Eight possible neighbors. + if (offset >= 4) ++offset; // Option 4 is self; we want 0-3 or 5-8 + const int offset_x = offset%3 - 1; // Set x offset as -1 through +1 + const int offset_y = offset/3 - 1; // Set y offset as -1 through +1 + const int rand_x = (int) (id%size_x) + offset_x; // Shift x by start position. + const int rand_y = (int) (id/size_x) + offset_y; // Shift y by start position. + const int neighbor_x = emp::Mod(rand_x, (int) size_x); // Ensure new x pos is on grid. + const int neighbor_y = emp::Mod(rand_y, (int) size_y); // Ensure new y pos is on grid. - const auto neighbor_id = ( - emp::Mod(rand_x, (int) size_x) - + emp::Mod(rand_y, (int) size_y) * (int)size_x - ); + // Combine into the new neighbor id. + const int neighbor_id = neighbor_x + neighbor_y * (int)size_x; - emp_assert((int)pos.GetIndex() != neighbor_id); + // The new ID should never be the old one. + emp_assert((int)pos.GetIndex() != neighbor_id, pos.GetIndex(), neighbor_id); return pos.SetIndex(neighbor_id); }; @@ -1287,7 +1295,7 @@ namespace emp { int p = random_ptr->GetInt(options.CountOnes()); std::cout << p << std::endl; while(p-- >= 0) { - rand_pos = options.PopBit(); + rand_pos = options.PopOne(); } } @@ -1545,7 +1553,7 @@ namespace emp { else new_org.Delete(); // Otherwise delete the organism. } - // Give birth to (potentially) multiple offspring; return position of last placed. + // Give birth to (potentially multiple) offspring; return position of last placed. // Triggers 'before repro' signal on parent (once) and 'offspring ready' on each offspring. // Additional signal triggers occur in AddOrgAt. template diff --git a/include/emp/Evolve/World_iterator.hpp b/include/emp/Evolve/World_iterator.hpp index 0950a4d98b..241d422fef 100644 --- a/include/emp/Evolve/World_iterator.hpp +++ b/include/emp/Evolve/World_iterator.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2017-2018 + * @date 2017-2021. * @note Originally called PopulationIterator.h * * @file World_iterator.hpp @@ -43,7 +43,7 @@ namespace emp { public: /// Create an iterator in the specified world pointing to the first occupied cell after the /// provided start position. - World_iterator(world_t * _w, size_t _p=0) : world_ptr(_w), pos(_p) { MakeValid(); } + World_iterator(Ptr _w, size_t _p=0) : world_ptr(_w), pos(_p) { MakeValid(); } /// Create an iterator pointing to the same position as another iterator. World_iterator(const World_iterator & _in) : world_ptr(_in.world_ptr), pos(_in.pos) { MakeValid(); } diff --git a/include/emp/Evolve/World_select.hpp b/include/emp/Evolve/World_select.hpp index 138f835d28..60f5ac3ba7 100644 --- a/include/emp/Evolve/World_select.hpp +++ b/include/emp/Evolve/World_select.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2017-2018 + * @date 2017-2021. * * @file World_select.hpp * @brief Functions for popular selection methods applied to worlds. @@ -15,11 +15,11 @@ #include "../base/array.hpp" #include "../base/assert.hpp" -#include "../base/macros.hpp" #include "../base/vector.hpp" #include "../datastructs/IndexMap.hpp" #include "../datastructs/vector_utils.hpp" #include "../math/Random.hpp" +#include "../meta/macros.hpp" #include "../meta/reflection.hpp" namespace emp { diff --git a/include/emp/LEVELS.txt b/include/emp/LEVELS.txt new file mode 100644 index 0000000000..ca2dc43f8f --- /dev/null +++ b/include/emp/LEVELS.txt @@ -0,0 +1,478 @@ +============ LEVEL 0 ============ +Action.hpp (control/Action.hpp) +BloomFilter.hpp (datastructs/BloomFilter.hpp) +Cache.hpp (datastructs/Cache.hpp) +MemoryIStream.hpp (io/MemoryIStream.hpp) +NullStream.hpp (io/NullStream.hpp) +OrgInterface.hpp (Evolve/OrgInterface.hpp) +PlusCountdownRegulator.hpp (matching/regulators/PlusCountdownRegulator.hpp) +_DepositoryEntry.hpp (matching/_DepositoryEntry.hpp) +_TableCell.hpp (web/_TableCell.hpp) +_TableCol.hpp (web/_TableCol.hpp) +_TableColGroup.hpp (web/_TableColGroup.hpp) +_TableRow.hpp (web/_TableRow.hpp) +_TableRowGroup.hpp (web/_TableRowGroup.hpp) +_assert_macros.hpp (base/_assert_macros.hpp) +_bitset_helpers.hpp (bits/_bitset_helpers.hpp) +_is_streamable.hpp (base/_is_streamable.hpp) +_tdebug_assert_trigger.hpp (base/_tdebug_assert_trigger.hpp) +bitset_utils.hpp (bits/bitset_utils.hpp) +class.hpp (in_progress/class.hpp) +constants.hpp (math/constants.hpp) +debug.hpp (debug/debug.hpp) +error.hpp (base/error.hpp) +errors.hpp (base/errors.hpp) +fixed.hpp (in_progress/fixed.hpp) +macro_math.hpp (meta/macro_math.hpp) +span.hpp (polyfill/span.hpp) +struct.hpp (in_progress/struct.hpp) +value_utils.hpp (tools/value_utils.hpp) +============ LEVEL 1 ============ +ConfigLexer.hpp (in_progress/ConfigLexer.hpp) + : errors.hpp(0) +_emscripten_assert_trigger.hpp (base/_emscripten_assert_trigger.hpp) + : _is_streamable.hpp(0) +_native_assert_trigger.hpp (base/_native_assert_trigger.hpp) + : _is_streamable.hpp(0) +macros.hpp (meta/macros.hpp) + : macro_math.hpp(0) +============ LEVEL 2 ============ +_assert_trigger.hpp (base/_assert_trigger.hpp) + : _emscripten_assert_trigger.hpp(1) _native_assert_trigger.hpp(1) _tdebug_assert_trigger.hpp(0) +serialize_macros.hpp (io/serialize_macros.hpp) + : macros.hpp(1) +============ LEVEL 3 ============ +always_assert.hpp (base/always_assert.hpp) + : _assert_macros.hpp(0) _assert_trigger.hpp(2) +always_assert_warning.hpp (base/always_assert_warning.hpp) + : _assert_macros.hpp(0) _assert_trigger.hpp(2) +============ LEVEL 4 ============ +assert.hpp (base/assert.hpp) + : always_assert.hpp(3) +assert_warning.hpp (base/assert_warning.hpp) + : always_assert_warning.hpp(3) +============ LEVEL 5 ============ +Bool.hpp (datastructs/Bool.hpp) + : assert.hpp(4) +GenericFunction.hpp (functional/GenericFunction.hpp) + : assert.hpp(4) +MapProxy.hpp (base/MapProxy.hpp) + : assert.hpp(4) +QueueCache.hpp (datastructs/QueueCache.hpp) + : assert.hpp(4) +array.hpp (base/array.hpp) + : assert.hpp(4) +emscripten_assert.hpp (base/emscripten_assert.hpp) + : assert.hpp(4) +optional.hpp (base/optional.hpp) + : assert.hpp(4) +timing.hpp (tools/timing.hpp) + : assert.hpp(4) constants.hpp(0) +unique.hpp (tools/unique.hpp) + : assert.hpp(4) constants.hpp(0) +vector.hpp (base/vector.hpp) + : assert.hpp(4) +============ LEVEL 6 ============ +BatchConfig.hpp (in_progress/BatchConfig.hpp) + : vector.hpp(5) +ConfigParser.hpp (in_progress/ConfigParser.hpp) + : ConfigLexer.hpp(1) vector.hpp(5) +ContiguousStream.hpp (io/ContiguousStream.hpp) + : vector.hpp(5) +DynamicString.hpp (datastructs/DynamicString.hpp) + : vector.hpp(5) +FunctionSet.hpp (functional/FunctionSet.hpp) + : vector.hpp(5) +IndexMap.hpp (datastructs/IndexMap.hpp) + : vector.hpp(5) +LinearCode.hpp (hardware/LinearCode.hpp) + : array.hpp(5) vector.hpp(5) +PayoffMatrix.hpp (games/PayoffMatrix.hpp) + : array.hpp(5) vector.hpp(5) +Ptr.hpp (base/Ptr.hpp) + : assert.hpp(4) vector.hpp(5) +Range.hpp (math/Range.hpp) + : assert.hpp(4) vector.hpp(5) +SmallFifoMap.hpp (datastructs/SmallFifoMap.hpp) + : array.hpp(5) +UnorderedIndexMap.hpp (datastructs/UnorderedIndexMap.hpp) + : vector.hpp(5) +combos.hpp (math/combos.hpp) + : assert.hpp(4) vector.hpp(5) +map.hpp (base/map.hpp) + : MapProxy.hpp(5) assert.hpp(4) +meta.hpp (meta/meta.hpp) + : vector.hpp(5) +serialize.hpp (io/serialize.hpp) + : serialize_macros.hpp(2) vector.hpp(5) +set_utils.hpp (datastructs/set_utils.hpp) + : vector.hpp(5) +unordered_map.hpp (base/unordered_map.hpp) + : MapProxy.hpp(5) assert.hpp(4) +valsort_map.hpp (datastructs/valsort_map.hpp) + : vector.hpp(5) +============ LEVEL 7 ============ +MemoryImage.hpp (data/MemoryImage.hpp) + : Ptr.hpp(6) assert.hpp(4) +Random.hpp (math/Random.hpp) + : Ptr.hpp(6) Range.hpp(6) assert.hpp(4) bitset_utils.hpp(0) +SystematicsAnalysis.hpp (Evolve/SystematicsAnalysis.hpp) + : Ptr.hpp(6) +Trait.hpp (in_progress/Trait.hpp) + : assert.hpp(4) meta.hpp(6) +TypePack.hpp (meta/TypePack.hpp) + : meta.hpp(6) +ValPack.hpp (meta/ValPack.hpp) + : meta.hpp(6) +World_iterator.hpp (Evolve/World_iterator.hpp) + : Ptr.hpp(6) +flex_function.hpp (functional/flex_function.hpp) + : assert.hpp(4) meta.hpp(6) +hash_utils.hpp (datastructs/hash_utils.hpp) + : Ptr.hpp(6) span.hpp(0) +map_utils.hpp (datastructs/map_utils.hpp) + : map.hpp(6) vector.hpp(5) +ra_set.hpp (datastructs/ra_set.hpp) + : map.hpp(6) vector.hpp(5) +reference_vector.hpp (datastructs/reference_vector.hpp) + : Ptr.hpp(6) vector.hpp(5) +tuple_struct.hpp (datastructs/tuple_struct.hpp) + : macros.hpp(1) meta.hpp(6) +type_traits.hpp (meta/type_traits.hpp) + : _is_streamable.hpp(0) meta.hpp(6) +============ LEVEL 8 ============ +AnyFunction.hpp (functional/AnyFunction.hpp) + : Ptr.hpp(6) assert.hpp(4) type_traits.hpp(7) +ConceptWrapper.hpp (meta/ConceptWrapper.hpp) + : TypePack.hpp(7) macros.hpp(1) meta.hpp(6) +Distribution.hpp (math/Distribution.hpp) + : Random.hpp(7) UnorderedIndexMap.hpp(6) +EventLib.hpp (hardware/EventLib.hpp) + : FunctionSet.hpp(6) map_utils.hpp(7) vector.hpp(5) +Signal.hpp (control/Signal.hpp) + : Action.hpp(0) FunctionSet.hpp(6) TypePack.hpp(7) map_utils.hpp(7) +StreamManager.hpp (io/StreamManager.hpp) + : Ptr.hpp(6) map_utils.hpp(7) +attrs.hpp (tools/attrs.hpp) + : TypePack.hpp(7) type_traits.hpp(7) +reflection.hpp (meta/reflection.hpp) + : TypePack.hpp(7) meta.hpp(6) +tuple_utils.hpp (datastructs/tuple_utils.hpp) + : ValPack.hpp(7) hash_utils.hpp(7) +============ LEVEL 9 ============ +World_reflect.hpp (Evolve/World_reflect.hpp) + : Random.hpp(7) assert.hpp(4) reflection.hpp(8) +math.hpp (math/math.hpp) + : Random.hpp(7) assert.hpp(4) constants.hpp(0) reflection.hpp(8) +memo_function.hpp (functional/memo_function.hpp) + : assert.hpp(4) meta.hpp(6) tuple_utils.hpp(8) +string_utils.hpp (tools/string_utils.hpp) + : Ptr.hpp(6) array.hpp(5) assert.hpp(4) reflection.hpp(8) type_traits.hpp(7) vector.hpp(5) +============ LEVEL 10 ============ +ActionManager.hpp (control/ActionManager.hpp) + : Action.hpp(0) string_utils.hpp(9) +Attributes.hpp (web/Attributes.hpp) + : errors.hpp(0) string_utils.hpp(9) +BitArray.hpp (bits/BitArray.hpp) + : Ptr.hpp(6) Random.hpp(7) _bitset_helpers.hpp(0) assert.hpp(4) bitset_utils.hpp(0) hash_utils.hpp(7) math.hpp(9) span.hpp(0) type_traits.hpp(7) vector.hpp(5) +BitVector.hpp (bits/BitVector.hpp) + : Ptr.hpp(6) Random.hpp(7) _bitset_helpers.hpp(0) assert.hpp(4) bitset_utils.hpp(0) hash_utils.hpp(7) math.hpp(9) span.hpp(0) vector.hpp(5) +ConfigManager.hpp (config/ConfigManager.hpp) + : errors.hpp(0) string_utils.hpp(9) +DFA.hpp (compiler/DFA.hpp) + : array.hpp(5) string_utils.hpp(9) vector.hpp(5) +DataNode.hpp (data/DataNode.hpp) + : FunctionSet.hpp(6) IndexMap.hpp(6) ValPack.hpp(7) assert.hpp(4) math.hpp(9) string_utils.hpp(9) vector.hpp(5) +File.hpp (io/File.hpp) + : string_utils.hpp(9) vector.hpp(5) +InstLib.hpp (hardware/InstLib.hpp) + : Ptr.hpp(6) array.hpp(5) map_utils.hpp(7) string_utils.hpp(9) vector.hpp(5) +Listeners.hpp (web/Listeners.hpp) + : string_utils.hpp(9) +Mancala.hpp (games/Mancala.hpp) + : array.hpp(5) assert.hpp(4) math.hpp(9) vector.hpp(5) +Othello.hpp (games/Othello.hpp) + : array.hpp(5) assert.hpp(4) math.hpp(9) vector.hpp(5) +Othello8.hpp (games/Othello8.hpp) + : array.hpp(5) assert.hpp(4) bitset_utils.hpp(0) math.hpp(9) vector.hpp(5) +Point2D.hpp (geometry/Point2D.hpp) + : math.hpp(9) +SignalManager.hpp (control/SignalManager.hpp) + : Signal.hpp(8) string_utils.hpp(9) +SmallVector.hpp (datastructs/SmallVector.hpp) + : assert.hpp(4) math.hpp(9) +StringMap.hpp (datastructs/StringMap.hpp) + : string_utils.hpp(9) unordered_map.hpp(6) +Style.hpp (web/Style.hpp) + : string_utils.hpp(9) +TypeTracker.hpp (tools/TypeTracker.hpp) + : GenericFunction.hpp(5) Ptr.hpp(6) array.hpp(5) assert.hpp(4) map_utils.hpp(7) math.hpp(9) meta.hpp(6) +alert.hpp (debug/alert.hpp) + : string_utils.hpp(9) +color_map.hpp (web/color_map.hpp) + : string_utils.hpp(9) vector.hpp(5) +command_line.hpp (config/command_line.hpp) + : string_utils.hpp(9) vector.hpp(5) +distances.hpp (math/distances.hpp) + : math.hpp(9) type_traits.hpp(7) +hash_namify.hpp (tools/hash_namify.hpp) + : string_utils.hpp(9) vector.hpp(5) +info_theory.hpp (math/info_theory.hpp) + : math.hpp(9) vector.hpp(5) +init.hpp (web/init.hpp) + : assert_warning.hpp(4) string_utils.hpp(9) +keyname_utils.hpp (tools/keyname_utils.hpp) + : assert.hpp(4) string_utils.hpp(9) vector.hpp(5) +sequence_utils.hpp (math/sequence_utils.hpp) + : math.hpp(9) vector.hpp(5) +vector_utils.hpp (datastructs/vector_utils.hpp) + : string_utils.hpp(9) vector.hpp(5) +============ LEVEL 11 ============ +Angle2D.hpp (geometry/Angle2D.hpp) + : Point2D.hpp(10) constants.hpp(0) +AvidaCPU_InstLib.hpp (hardware/AvidaCPU_InstLib.hpp) + : InstLib.hpp(10) math.hpp(9) +BitSet.hpp (bits/BitSet.hpp) + : BitArray.hpp(10) +BitSorter.hpp (hardware/BitSorter.hpp) + : bitset_utils.hpp(0) vector.hpp(5) vector_utils.hpp(10) +Circle2D.hpp (geometry/Circle2D.hpp) + : Point2D.hpp(10) +DataFile.hpp (data/DataFile.hpp) + : DataNode.hpp(10) FunctionSet.hpp(6) NullStream.hpp(0) assert.hpp(4) string_utils.hpp(9) type_traits.hpp(7) vector.hpp(5) +DataInterface.hpp (data/DataInterface.hpp) + : DataNode.hpp(10) +DataManager.hpp (data/DataManager.hpp) + : DataNode.hpp(10) assert.hpp(4) map_utils.hpp(7) +Font.hpp (web/Font.hpp) + : Style.hpp(10) color_map.hpp(10) +Graph.hpp (datastructs/Graph.hpp) + : BitVector.hpp(10) assert.hpp(4) vector.hpp(5) +MatchDepository.hpp (matching/MatchDepository.hpp) + : SmallFifoMap.hpp(6) SmallVector.hpp(10) _DepositoryEntry.hpp(0) +NK.hpp (Evolve/NK.hpp) + : BitVector.hpp(10) Random.hpp(7) math.hpp(9) memo_function.hpp(9) vector.hpp(5) +RankedSelector.hpp (matching/selectors_static/RankedSelector.hpp) + : SmallVector.hpp(10) vector.hpp(5) +SettingCombos.hpp (config/SettingCombos.hpp) + : Ptr.hpp(6) map_utils.hpp(7) math.hpp(9) string_utils.hpp(9) vector.hpp(5) vector_utils.hpp(10) +SettingConfig.hpp (config/SettingConfig.hpp) + : Ptr.hpp(6) command_line.hpp(10) map_utils.hpp(7) math.hpp(9) string_utils.hpp(9) vector.hpp(5) vector_utils.hpp(10) +SignalControl.hpp (control/SignalControl.hpp) + : ActionManager.hpp(10) SignalManager.hpp(10) +SolveState.hpp (tools/SolveState.hpp) + : BitVector.hpp(10) assert.hpp(4) +StateGrid.hpp (Evolve/StateGrid.hpp) + : BitVector.hpp(10) File.hpp(10) Ptr.hpp(6) Random.hpp(7) assert.hpp(4) map_utils.hpp(7) math.hpp(9) vector.hpp(5) +TimeQueue.hpp (datastructs/TimeQueue.hpp) + : assert.hpp(4) vector.hpp(5) vector_utils.hpp(10) +TypeID.hpp (meta/TypeID.hpp) + : Ptr.hpp(6) TypePack.hpp(7) string_utils.hpp(9) type_traits.hpp(7) vector.hpp(5) vector_utils.hpp(10) +WidgetExtras.hpp (web/WidgetExtras.hpp) + : Attributes.hpp(10) Listeners.hpp(10) Style.hpp(10) init.hpp(10) +World_select.hpp (Evolve/World_select.hpp) + : IndexMap.hpp(6) Random.hpp(7) array.hpp(5) assert.hpp(4) macros.hpp(1) reflection.hpp(8) vector.hpp(5) vector_utils.hpp(10) +World_structure.hpp (Evolve/World_structure.hpp) + : Random.hpp(7) Trait.hpp(7) array.hpp(5) assert.hpp(4) math.hpp(9) vector.hpp(5) vector_utils.hpp(10) +ascii_utils.hpp (io/ascii_utils.hpp) + : assert.hpp(4) vector.hpp(5) vector_utils.hpp(10) +config.hpp (config/config.hpp) + : ConfigManager.hpp(10) errors.hpp(0) macros.hpp(1) map_utils.hpp(7) string_utils.hpp(9) unordered_map.hpp(6) vector.hpp(5) +js_utils.hpp (web/js_utils.hpp) + : array.hpp(5) assert.hpp(4) init.hpp(10) map.hpp(6) vector.hpp(5) +mem_track.hpp (debug/mem_track.hpp) + : alert.hpp(10) map.hpp(6) +random_utils.hpp (math/random_utils.hpp) + : BitVector.hpp(10) Random.hpp(7) vector.hpp(5) +stats.hpp (math/stats.hpp) + : map.hpp(6) math.hpp(9) type_traits.hpp(7) vector.hpp(5) vector_utils.hpp(10) +unit_tests.hpp (testing/unit_tests.hpp) + : command_line.hpp(10) string_utils.hpp(9) +============ LEVEL 12 ============ +ArgManager.hpp (config/ArgManager.hpp) + : Ptr.hpp(6) command_line.hpp(10) config.hpp(11) optional.hpp(5) vector.hpp(5) +AvidaGP.hpp (hardware/AvidaGP.hpp) + : AvidaCPU_InstLib.hpp(11) File.hpp(10) Ptr.hpp(6) Random.hpp(7) array.hpp(5) map_utils.hpp(7) string_utils.hpp(9) vector.hpp(5) +BitMatrix.hpp (bits/BitMatrix.hpp) + : BitSet.hpp(11) bitset_utils.hpp(0) +Body2D.hpp (geometry/Body2D.hpp) + : Angle2D.hpp(11) Circle2D.hpp(11) Ptr.hpp(6) alert.hpp(10) assert.hpp(4) mem_track.hpp(11) vector.hpp(5) +DataLayout.hpp (data/DataLayout.hpp) + : MemoryImage.hpp(7) TypeID.hpp(11) assert.hpp(4) map_utils.hpp(7) vector.hpp(5) +DataLog.hpp (data/DataLog.hpp) + : ascii_utils.hpp(11) assert.hpp(4) stats.hpp(11) vector.hpp(5) vector_utils.hpp(10) +JSWrap.hpp (web/JSWrap.hpp) + : assert.hpp(4) init.hpp(10) js_utils.hpp(11) mem_track.hpp(11) meta.hpp(6) tuple_struct.hpp(7) tuple_utils.hpp(8) vector.hpp(5) +NFA.hpp (compiler/NFA.hpp) + : BitSet.hpp(11) set_utils.hpp(6) vector.hpp(5) +NK-const.hpp (Evolve/NK-const.hpp) + : BitSet.hpp(11) Random.hpp(7) assert.hpp(4) math.hpp(9) +Surface.hpp (geometry/Surface.hpp) + : Angle2D.hpp(11) Point2D.hpp(10) Ptr.hpp(6) TypePack.hpp(7) TypeTracker.hpp(10) vector_utils.hpp(10) +Systematics.hpp (Evolve/Systematics.hpp) + : DataFile.hpp(11) DataManager.hpp(11) DataNode.hpp(10) Ptr.hpp(6) Signal.hpp(8) SystematicsAnalysis.hpp(7) World_structure.hpp(11) info_theory.hpp(10) map_utils.hpp(7) set_utils.hpp(6) stats.hpp(11) string_utils.hpp(9) +TypeMap.hpp (datastructs/TypeMap.hpp) + : TypeID.hpp(11) +VarMap.hpp (data/VarMap.hpp) + : Ptr.hpp(6) TypeID.hpp(11) assert.hpp(4) map_utils.hpp(7) string_utils.hpp(9) unordered_map.hpp(6) vector.hpp(5) +World_output.hpp (Evolve/World_output.hpp) + : DataFile.hpp(11) SystematicsAnalysis.hpp(7) string_utils.hpp(9) vector.hpp(5) +config_utils.hpp (config/config_utils.hpp) + : config.hpp(11) +graph_utils.hpp (datastructs/graph_utils.hpp) + : Graph.hpp(11) Random.hpp(7) assert.hpp(4) random_utils.hpp(11) vector.hpp(5) vector_utils.hpp(10) +matchbin_metrics.hpp (matching/matchbin_metrics.hpp) + : BitSet.hpp(11) Distribution.hpp(8) IndexMap.hpp(6) array.hpp(5) assert.hpp(4) hash_utils.hpp(7) math.hpp(9) span.hpp(0) string_utils.hpp(9) tuple_utils.hpp(8) vector.hpp(5) +matchbin_regulators.hpp (matching/matchbin_regulators.hpp) + : BitSet.hpp(11) Distribution.hpp(8) IndexMap.hpp(6) array.hpp(5) assert.hpp(4) hash_utils.hpp(7) math.hpp(9) string_utils.hpp(9) vector.hpp(5) +matchbin_selectors.hpp (matching/matchbin_selectors.hpp) + : BitSet.hpp(11) Distribution.hpp(8) IndexMap.hpp(6) array.hpp(5) assert.hpp(4) hash_utils.hpp(7) math.hpp(9) optional.hpp(5) string_utils.hpp(9) vector.hpp(5) +============ LEVEL 13 ============ +DataMap.hpp (data/DataMap.hpp) + : DataLayout.hpp(12) MemoryImage.hpp(7) Ptr.hpp(6) TypeID.hpp(11) assert.hpp(4) string_utils.hpp(9) +OEE.hpp (Evolve/OEE.hpp) + : BloomFilter.hpp(0) Ptr.hpp(6) Systematics.hpp(12) set_utils.hpp(6) vector.hpp(5) vector_utils.hpp(10) +Surface2D.hpp (geometry/Surface2D.hpp) + : Body2D.hpp(12) Ptr.hpp(6) +UrlParams.hpp (web/UrlParams.hpp) + : JSWrap.hpp(12) js_utils.hpp(11) +World.hpp (Evolve/World.hpp) + : BitSet.hpp(11) DataFile.hpp(11) DataManager.hpp(11) Ptr.hpp(6) Random.hpp(7) Range.hpp(6) Signal.hpp(8) SignalControl.hpp(11) Systematics.hpp(12) Trait.hpp(7) World_iterator.hpp(7) World_reflect.hpp(9) World_select.hpp(11) World_structure.hpp(11) map_utils.hpp(7) random_utils.hpp(11) reflection.hpp(8) string_utils.hpp(9) vector.hpp(5) +emfunctions.hpp (web/emfunctions.hpp) + : JSWrap.hpp(12) alert.hpp(10) string_utils.hpp(9) +events.hpp (web/events.hpp) + : JSWrap.hpp(12) +lexer_utils.hpp (compiler/lexer_utils.hpp) + : BitVector.hpp(10) DFA.hpp(10) NFA.hpp(12) vector.hpp(5) +matchbin_utils.hpp (matching/matchbin_utils.hpp) + : matchbin_metrics.hpp(12) matchbin_regulators.hpp(12) matchbin_selectors.hpp(12) +utils.hpp (web/d3/utils.hpp) + : JSWrap.hpp(12) init.hpp(10) js_utils.hpp(11) macros.hpp(1) +============ LEVEL 14 ============ +KeypressManager.hpp (web/KeypressManager.hpp) + : JSWrap.hpp(12) errors.hpp(0) events.hpp(13) +MatchBin.hpp (matching/MatchBin.hpp) + : BitSet.hpp(11) DataFile.hpp(11) DataNode.hpp(10) IndexMap.hpp(6) assert.hpp(4) errors.hpp(0) matchbin_utils.hpp(13) optional.hpp(5) vector.hpp(5) +Physics2D.hpp (geometry/Physics2D.hpp) + : Ptr.hpp(6) Surface2D.hpp(13) vector.hpp(5) +RawImage.hpp (web/RawImage.hpp) + : JSWrap.hpp(12) Ptr.hpp(6) Signal.hpp(8) emfunctions.hpp(13) map_utils.hpp(7) vector.hpp(5) +RegEx.hpp (compiler/RegEx.hpp) + : BitSet.hpp(11) NFA.hpp(12) Ptr.hpp(6) lexer_utils.hpp(13) string_utils.hpp(9) vector.hpp(5) +Resource.hpp (Evolve/Resource.hpp) + : World.hpp(13) +Widget.hpp (web/Widget.hpp) + : Font.hpp(11) Signal.hpp(8) WidgetExtras.hpp(11) errors.hpp(0) events.hpp(13) init.hpp(10) mem_track.hpp(11) vector.hpp(5) +d3_init.hpp (web/d3/d3_init.hpp) + : JSWrap.hpp(12) errors.hpp(0) init.hpp(10) utils.hpp(13) +spatial_stats.hpp (math/spatial_stats.hpp) + : World.hpp(13) stats.hpp(11) vector.hpp(5) +============ LEVEL 15 ============ +Button.hpp (web/Button.hpp) + : Widget.hpp(14) init.hpp(10) +CanvasAction.hpp (web/CanvasAction.hpp) + : Point2D.hpp(10) RawImage.hpp(14) Widget.hpp(14) +EventDrivenGP.hpp (hardware/EventDrivenGP.hpp) + : BitSet.hpp(11) BitVector.hpp(10) EventLib.hpp(8) InstLib.hpp(10) MatchBin.hpp(14) Ptr.hpp(6) Random.hpp(7) Signal.hpp(8) SignalControl.hpp(11) array.hpp(5) map_utils.hpp(7) string_utils.hpp(9) vector.hpp(5) +FileInput.hpp (web/FileInput.hpp) + : File.hpp(10) Widget.hpp(14) +Image.hpp (web/Image.hpp) + : Widget.hpp(14) +Input.hpp (web/Input.hpp) + : Widget.hpp(14) +Lexer.hpp (compiler/Lexer.hpp) + : RegEx.hpp(14) lexer_utils.hpp(13) map.hpp(6) map_utils.hpp(7) vector.hpp(5) +Selector.hpp (web/Selector.hpp) + : JSWrap.hpp(12) Widget.hpp(14) vector.hpp(5) +Text.hpp (web/Text.hpp) + : DynamicString.hpp(6) Widget.hpp(14) +TextArea.hpp (web/TextArea.hpp) + : Widget.hpp(14) +TextFeed.hpp (web/TextFeed.hpp) + : Widget.hpp(14) +Tween.hpp (web/Tween.hpp) + : Widget.hpp(14) emfunctions.hpp(13) +_FacetedWidget.hpp (web/_FacetedWidget.hpp) + : Widget.hpp(14) +commands.hpp (web/commands.hpp) + : Widget.hpp(14) +dataset.hpp (web/d3/dataset.hpp) + : JSWrap.hpp(12) d3_init.hpp(14) js_utils.hpp(11) string_utils.hpp(9) +scales.hpp (web/d3/scales.hpp) + : d3_init.hpp(14) js_utils.hpp(11) utils.hpp(13) +============ LEVEL 16 ============ +Animate.hpp (web/Animate.hpp) + : Button.hpp(15) JSWrap.hpp(12) Widget.hpp(14) assert.hpp(4) emfunctions.hpp(13) vector.hpp(5) +Author.hpp (scholar/Author.hpp) + : Lexer.hpp(15) +CanvasShape.hpp (web/CanvasShape.hpp) + : CanvasAction.hpp(15) Circle2D.hpp(11) vector.hpp(5) +Parser.hpp (in_progress/Parser.hpp) + : BitVector.hpp(10) Lexer.hpp(15) vector.hpp(5) +histogram.hpp (web/d3/histogram.hpp) + : d3_init.hpp(14) dataset.hpp(15) js_utils.hpp(11) tuple_struct.hpp(7) vector.hpp(5) +selection.hpp (web/d3/selection.hpp) + : JSWrap.hpp(12) assert.hpp(4) d3_init.hpp(14) dataset.hpp(15) js_utils.hpp(11) utils.hpp(13) +signalgp_utils.hpp (hardware/signalgp_utils.hpp) + : BitSet.hpp(11) EventDrivenGP.hpp(15) Random.hpp(7) errors.hpp(0) map_utils.hpp(7) math.hpp(9) random_utils.hpp(11) +============ LEVEL 17 ============ +Canvas.hpp (web/Canvas.hpp) + : CanvasAction.hpp(15) CanvasShape.hpp(16) Circle2D.hpp(11) string_utils.hpp(9) vector.hpp(5) +Citation.hpp (scholar/Citation.hpp) + : Author.hpp(16) map.hpp(6) map_utils.hpp(7) string_utils.hpp(9) vector.hpp(5) +Div.hpp (web/Div.hpp) + : Animate.hpp(16) Text.hpp(15) Widget.hpp(14) init.hpp(10) +DocuExtras.hpp (web/DocuExtras.hpp) + : Animate.hpp(16) Text.hpp(15) Widget.hpp(14) init.hpp(10) +axis.hpp (web/d3/axis.hpp) + : d3_init.hpp(14) js_utils.hpp(11) scales.hpp(15) selection.hpp(16) string_utils.hpp(9) +svg_shapes.hpp (web/d3/svg_shapes.hpp) + : d3_init.hpp(14) dataset.hpp(15) js_utils.hpp(11) scales.hpp(15) selection.hpp(16) +visual_elements.hpp (web/d3/visual_elements.hpp) + : JSWrap.hpp(12) selection.hpp(16) vector.hpp(5) +============ LEVEL 18 ============ +Bibliography.hpp (scholar/Bibliography.hpp) + : Citation.hpp(17) vector.hpp(5) +Collapse.hpp (prefab/Collapse.hpp) + : Div.hpp(17) Widget.hpp(14) _FacetedWidget.hpp(15) string_utils.hpp(9) +CommentBox.hpp (prefab/CommentBox.hpp) + : Div.hpp(17) string_utils.hpp(9) +Element.hpp (web/Element.hpp) + : Animate.hpp(16) Div.hpp(17) Text.hpp(15) Widget.hpp(14) init.hpp(10) +Table.hpp (web/Table.hpp) + : Div.hpp(17) Widget.hpp(14) WidgetExtras.hpp(11) _TableCell.hpp(0) _TableCol.hpp(0) _TableColGroup.hpp(0) _TableRow.hpp(0) _TableRowGroup.hpp(0) vector.hpp(5) +canvas_utils.hpp (web/canvas_utils.hpp) + : BitMatrix.hpp(12) Canvas.hpp(17) Circle2D.hpp(11) StateGrid.hpp(11) Surface.hpp(12) Surface2D.hpp(13) color_map.hpp(10) vector.hpp(5) +layout.hpp (web/d3/layout.hpp) + : JSWrap.hpp(12) d3_init.hpp(14) dataset.hpp(15) selection.hpp(16) svg_shapes.hpp(17) tuple_struct.hpp(7) +============ LEVEL 19 ============ +CodeBlock.hpp (prefab/CodeBlock.hpp) + : Element.hpp(18) Widget.hpp(14) errors.hpp(0) string_utils.hpp(9) +Document.hpp (web/Document.hpp) + : Button.hpp(15) Canvas.hpp(17) Div.hpp(17) Element.hpp(18) FileInput.hpp(15) Image.hpp(15) Input.hpp(15) Selector.hpp(15) Table.hpp(18) Text.hpp(15) TextArea.hpp(15) canvas_utils.hpp(18) color_map.hpp(10) events.hpp(13) +FontAwesomeIcon.hpp (prefab/FontAwesomeIcon.hpp) + : Div.hpp(17) Element.hpp(18) Widget.hpp(14) string_utils.hpp(9) +LoadingIcon.hpp (prefab/LoadingIcon.hpp) + : Div.hpp(17) Element.hpp(18) Widget.hpp(14) errors.hpp(0) string_utils.hpp(9) +LoadingModal.hpp (prefab/LoadingModal.hpp) + : Div.hpp(17) Element.hpp(18) Widget.hpp(14) string_utils.hpp(9) +Modal.hpp (prefab/Modal.hpp) + : Button.hpp(15) Div.hpp(17) Element.hpp(18) Widget.hpp(14) string_utils.hpp(9) +ToggleSwitch.hpp (prefab/ToggleSwitch.hpp) + : Element.hpp(18) Input.hpp(15) Widget.hpp(14) string_utils.hpp(9) +config_web_interface.hpp (config/config_web_interface.hpp) + : Div.hpp(17) Element.hpp(18) Input.hpp(15) config.hpp(11) set_utils.hpp(6) string_utils.hpp(9) +visualizations.hpp (web/d3/visualizations.hpp) + : Animate.hpp(16) BitSet.hpp(11) FunctionSet.hpp(6) JSWrap.hpp(12) Random.hpp(7) axis.hpp(17) config.hpp(11) constants.hpp(0) histogram.hpp(16) init.hpp(10) layout.hpp(18) scales.hpp(15) selection.hpp(16) stats.hpp(11) string_utils.hpp(9) svg_shapes.hpp(17) visual_elements.hpp(17) +============ LEVEL 20 ============ +Card.hpp (prefab/Card.hpp) + : Collapse.hpp(18) Div.hpp(17) FontAwesomeIcon.hpp(19) string_utils.hpp(9) +NodeDomShim.hpp (web/NodeDomShim.hpp) + : Document.hpp(19) string_utils.hpp(9) vector.hpp(5) +_MochaTestRunner.hpp (web/_MochaTestRunner.hpp) + : Document.hpp(19) JSWrap.hpp(12) Signal.hpp(8) unit_tests.hpp(11) vector.hpp(5) +web.hpp (web/web.hpp) + : Document.hpp(19) +============ LEVEL 21 ============ +ConfigPanel.hpp (prefab/ConfigPanel.hpp) + : Card.hpp(20) Collapse.hpp(18) CommentBox.hpp(18) Div.hpp(17) Element.hpp(18) FontAwesomeIcon.hpp(19) Input.hpp(15) ToggleSwitch.hpp(19) config.hpp(11) set_utils.hpp(6) string_utils.hpp(9) diff --git a/include/emp/base/Ptr.hpp b/include/emp/base/Ptr.hpp index dc2fff8e3f..f441546462 100644 --- a/include/emp/base/Ptr.hpp +++ b/include/emp/base/Ptr.hpp @@ -11,6 +11,9 @@ * compiled with EMP_TRACK_MEM set, then these pointers perform extra tests to ensure that * they point to valid memory and that memory is freed before pointers are released. * + * If you want to prevent pointers to pointers (a common source of errors, but MAY be done + * intentionally) you can define EMP_NO_PTR_TO_PTR + * * If you trip an assert, you can re-do the run a track a specific pointer by defining * EMP_ABORT_PTR_NEW or EMP_ABORT_PTR_DELETE to the ID of the pointer in question. This will * allow you to track the pointer more easily in a debugger. @@ -24,6 +27,7 @@ #ifndef EMP_PTR_H #define EMP_PTR_H +#include #include #include "assert.hpp" @@ -31,6 +35,18 @@ namespace emp { + // ------------ Pre-declare some helper types and functions -------------- + + template class Ptr; + + + template + inline void FillMemory(emp::Ptr mem_ptr, const size_t num_bytes, T fill_value); + + /// Fill an array by repeatedly calling the provided fill functions. + template + inline void FillMemoryFunction(emp::Ptr mem_ptr, const size_t num_bytes, T fill_fun); + #ifndef DOXYGEN_SHOULD_SKIP_THIS namespace internal { /// An anonymous log2 calculator for hashing below. @@ -92,14 +108,14 @@ namespace emp { void SetArray(size_t bytes) noexcept { array_bytes = bytes; status = PtrStatus::ARRAY; } /// Add one more pointer. - void Inc(const size_t id) { + void Inc([[maybe_unused]] const size_t id) { if (internal::ptr_debug) std::cout << "Inc info for pointer " << ptr << std::endl; emp_assert(status != PtrStatus::DELETED, "Incrementing deleted pointer!", id); count++; } /// Remove a pointer. - void Dec(const size_t id) { + void Dec([[maybe_unused]] const size_t id) { if (internal::ptr_debug) std::cout << "Dec info for pointer " << ptr << std::endl; // Make sure that we have more than one copy, -or- we've already deleted this pointer @@ -232,6 +248,8 @@ namespace emp { /// Is an ID associated with an array? bool IsArrayID(size_t id) { if (internal::ptr_debug) std::cout << "IsArrayID: " << id << std::endl; + if (id == UNTRACKED_ID) return false; + if (id >= id_info.size()) return false; return id_info[id].IsArray(); } @@ -310,7 +328,7 @@ namespace emp { namespace { // @CAO: Build this for real! template - bool PtrIsConvertable(FROM * ptr) { return true; } + bool PtrIsConvertable(FROM * ptr) { (void) ptr; return true; } // emp_assert( (std::is_same() || dynamic_cast(in_ptr)) ); // Debug information provided for each pointer type. @@ -330,7 +348,13 @@ namespace emp { TYPE * ptr; ///< The raw pointer associated with this Ptr object. size_t id; ///< A unique ID for this pointer type. - BasePtr(TYPE * in_ptr, size_t in_id) : ptr(in_ptr), id(in_id) { } + static constexpr size_t UNTRACKED_ID = (size_t) -1; + + BasePtr(TYPE * in_ptr, size_t in_id) : ptr(in_ptr), id(in_id) { + #ifdef EMP_NO_PTR_TO_PTR + emp_assert(!std::is_pointer_v, "Pointers to pointers are disallowed!"); + #endif + } static PtrTracker & Tracker() { return PtrTracker::Get(); } // Single tracker for al Ptr types @@ -353,8 +377,8 @@ namespace emp { /// Indexing into array TYPE & operator[](size_t pos) const { emp_assert(Tracker().IsDeleted(id) == false /*, typeid(TYPE).name() */, id); - emp_assert(Tracker().IsArrayID(id), "Only arrays can be indexed into.", id); - emp_assert(Tracker().GetArrayBytes(id) > (pos*sizeof(TYPE)), + emp_assert(id == UNTRACKED_ID || Tracker().IsArrayID(id), "Only arrays can be indexed into.", id); + emp_assert(id == UNTRACKED_ID || Tracker().GetArrayBytes(id) > (pos*sizeof(TYPE)), "Indexing out of range.", id, ptr, pos, sizeof(TYPE), Tracker().GetArrayBytes(id)); emp_assert(ptr != nullptr, "Do not follow a null pointer!"); return ptr[pos]; @@ -518,6 +542,9 @@ namespace emp { template [[nodiscard]] Ptr ReinterpretCast() const { emp_assert(Tracker().IsDeleted(id) == false, "Do not cast deleted pointers.", id); + #ifdef EMP_NO_PTR_TO_PTR + emp_assert(!std::is_pointer_v, "Reinterpreting as pointers to pointers is disallowed!"); + #endif return reinterpret_cast(ptr); } @@ -540,8 +567,9 @@ namespace emp { } /// Reallocate this Ptr to a newly allocated array using the size passed in. - template - void NewArray(size_t array_size, Ts &&... args) { + // template + // void NewArray(size_t array_size, Ts &&... args) { + void NewArray(size_t array_size) { Tracker().DecID(id); // Remove a pointer to any old memory... // @CAO: This next portion of code is allocating an array of the appropriate type. @@ -585,12 +613,12 @@ namespace emp { } /// Convert this pointer to a hash value. - size_t Hash() const { + size_t Hash() const noexcept { // Chop off useless bits of pointer... static constexpr size_t shift = internal::Log2(1 + sizeof(TYPE)); return (size_t)(ptr) >> shift; } - struct hash_t { size_t operator()(const Ptr & t) const { return t.Hash(); } }; + struct hash_t { size_t operator()(const Ptr & t) const noexcept { return t.Hash(); } }; /// Copy assignment Ptr & operator=(const Ptr & _in) { @@ -653,7 +681,7 @@ namespace emp { emp_assert(Tracker().IsDeleted(id) == false /*, typeid(TYPE).name() */, id); // We should not automatically convert managed pointers to raw pointers; use .Raw() - emp_assert(id == UNTRACKED_ID /*, typeid(TYPE).name() */, id, + emp_assert(id != UNTRACKED_ID /*, typeid(TYPE).name() */, id, "Use Raw() to convert to an untracked Ptr"); return ptr; } @@ -701,6 +729,38 @@ namespace emp { /// Does this Ptr point to a memory position after or equal to a raw pointer? bool operator>=(const TYPE * in_ptr) const { return ptr >= in_ptr; } + [[nodiscard]] Ptr operator+(int value) const { return ptr + value; } + [[nodiscard]] Ptr operator-(int value) const { return ptr - value; } + [[nodiscard]] Ptr operator+(size_t value) const { return ptr + value; } + [[nodiscard]] Ptr operator-(size_t value) const { return ptr - value; } + + /// Fill an array with the provided fill_value. + /// If fill_value is a function, repeatedly call function. + template + void FillMemoryFunction(const size_t num_bytes, T fill_fun) { + // Make sure a pointer is active before we write to it. + emp_assert(Tracker().IsDeleted(id) == false /*, typeid(TYPE).name() */, id); + emp_assert(id == UNTRACKED_ID || Tracker().IsArrayID(id), "Only arrays can fill memory.", id); + emp_assert(id == UNTRACKED_ID || Tracker().GetArrayBytes(id) >= num_bytes, + "Overfilling memory.", id, ptr, sizeof(TYPE), Tracker().GetArrayBytes(id)); + emp_assert(ptr != nullptr, "Do not follow a null pointer!"); + + emp::FillMemoryFunction(*this, num_bytes, fill_fun); + } + + /// Fill an array with the provided fill_value. + /// If fill_value is a function, repeatedly call function. + template + void FillMemory(const size_t num_bytes, T fill_value) { + // Make sure a pointer is active before we write to it. + emp_assert(Tracker().IsDeleted(id) == false /*, typeid(TYPE).name() */, id); + emp_assert(Tracker().IsArrayID(id) || id == UNTRACKED_ID, "Only arrays can fill memory.", id); + emp_assert(Tracker().GetArrayBytes(id) >= num_bytes, + "Overfilling memory.", id, ptr, sizeof(TYPE), Tracker().GetArrayBytes(id)); + emp_assert(ptr != nullptr, "Do not follow a null pointer!"); + + emp::FillMemory(*this, num_bytes, fill_value); + } /// Some debug testing functions int DebugGetCount() const { return Tracker().GetIDCount(id); } @@ -738,7 +798,7 @@ namespace emp { }; -#else // #ifdef EMP_TRACK_MEM +#else // EMP_MEM_TRACK off... template @@ -817,11 +877,11 @@ namespace emp { void Delete() { delete ptr; } void DeleteArray() { delete [] ptr; } - size_t Hash() const { + size_t Hash() const noexcept { static constexpr size_t shift = internal::Log2(1 + sizeof(TYPE)); // Chop off useless bits... return (size_t)(ptr) >> shift; } - struct hash_t { size_t operator()(const Ptr & t) const { return t.Hash(); } }; + struct hash_t { size_t operator()(const Ptr & t) const noexcept { return t.Hash(); } }; // Copy assignments Ptr & operator=(const Ptr & _in) { ptr = _in.ptr; return *this; } @@ -852,6 +912,27 @@ namespace emp { bool operator>(const TYPE * in_ptr) const { return ptr > in_ptr; } bool operator>=(const TYPE * in_ptr) const { return ptr >= in_ptr; } + [[nodiscard]] Ptr operator+(int value) const { return ptr + value; } + [[nodiscard]] Ptr operator-(int value) const { return ptr - value; } + [[nodiscard]] Ptr operator+(size_t value) const { return ptr + value; } + [[nodiscard]] Ptr operator-(size_t value) const { return ptr - value; } + + // Extra functionality (not in raw pointers) + + /// Fill an array with the provided fill_value. + /// If fill_value is a function, repeatedly call function. + template + void FillMemoryFunction(const size_t num_bytes, T fill_fun) { + emp::FillMemoryFunction(*this, num_bytes, fill_fun); + } + + /// Fill an array with the provided fill_value. + /// If fill_value is a function, repeatedly call function. + template + void FillMemory(const size_t num_bytes, T fill_value) { + emp::FillMemory(*this, num_bytes, fill_value); + } + // Stubs for debug-related functions when outside debug mode. int DebugGetCount() const { return -1; } bool DebugIsArray() const { emp_assert(false); return false; } @@ -918,8 +999,8 @@ namespace emp { } /// Create a pointer to an array of objects. - template - [[nodiscard]] Ptr NewArrayPtr(size_t array_size, ARGS &&... args) { + template + [[nodiscard]] Ptr NewArrayPtr(size_t array_size) { auto ptr = new T[array_size]; // Build a new raw pointer. // const size_t alloc_size = array_size * sizeof(T); // auto ptr = (T*) malloc (alloc_size); @@ -930,6 +1011,54 @@ namespace emp { return Ptr(ptr, array_size, true); } + /// Fill an array with the provided fill_value. + /// If fill_value is a function, repeatedly call function. + template + void FillMemory(emp::Ptr mem_ptr, const size_t num_bytes, T fill_value) { + // If the fill value is a function, call that function for each memory position. + if constexpr (std::is_invocable_v) { + FillMemoryFunction(mem_ptr, num_bytes, std::forward(fill_value)); + } + + constexpr size_t FILL_SIZE = sizeof(T); + + const size_t leftover = num_bytes % FILL_SIZE; + const size_t limit = num_bytes - leftover; + unsigned char * dest = mem_ptr.Raw(); + + // Fill out random bytes in groups of FILL_SIZE. + for (size_t byte = 0; byte < limit; byte += FILL_SIZE) { + std::memcpy(dest+byte, &fill_value, FILL_SIZE); + } + + // If we don't have a multiple of FILL_SIZE, fill in part of the remaining. + if (leftover) std::memcpy(dest+limit, &fill_value, leftover); + } + + /// Fill an array by repeatedly calling the provided fill functions. + template + void FillMemoryFunction(emp::Ptr mem_ptr, const size_t num_bytes, T fill_fun) { + static_assert(std::is_invocable_v, "FillMemoryFunction requires an invocable fill_fun."); + using return_t = decltype(fill_fun()); + constexpr size_t FILL_SIZE = sizeof(return_t); + + const size_t leftover = num_bytes % FILL_SIZE; + const size_t limit = num_bytes - leftover; + unsigned char * dest = mem_ptr.Raw(); + + // Fill out random bytes in groups of FILL_SIZE. + return_t fill_value; + for (size_t byte = 0; byte < limit; byte += FILL_SIZE) { + fill_value = fill_fun(); + std::memcpy(dest+byte, &fill_value, FILL_SIZE); + } + + // If we don't have a multiple of FILL_SIZE, fill in part of the remaining. + if (leftover) { + fill_value = fill_fun(); + std::memcpy(dest+limit, &fill_value, leftover); + } + } } // namespace emp diff --git a/include/emp/base/_assert_macros.hpp b/include/emp/base/_assert_macros.hpp new file mode 100644 index 0000000000..8a402a6658 --- /dev/null +++ b/include/emp/base/_assert_macros.hpp @@ -0,0 +1,52 @@ +/** + * @note This file is part of Empirical, https://github.com/devosoft/Empirical + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file _assert_macros.hpp + * @brief Helper macros for building proper assert commands. + * @note Status: RELEASE + * + */ + +#ifndef EMP_ASSERT_MACROS_HPP +#define EMP_ASSERT_MACROS_HPP + +/// Basic elper macros... +#define emp_assert_STRINGIFY(...) emp_assert_STRINGIFY_IMPL(__VA_ARGS__) +#define emp_assert_STRINGIFY_IMPL(...) #__VA_ARGS__ +#define emp_assert_TO_PAIR(X) emp_assert_STRINGIFY(X) , X +#define emp_assert_GET_ARG_1(a, ...) a +#define emp_assert_GET_ARG_21(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t, u, ...) u +#define emp_assert_MERGE(A, B) A ## B +#define emp_assert_ASSEMBLE(BASE, ARG_COUNT, ...) emp_assert_MERGE(BASE, ARG_COUNT) (__VA_ARGS__) + +/// returns the number of arguments in the __VA_ARGS__; cap of 20! +#define emp_assert_COUNT_ARGS(...) emp_assert_GET_ARG_21(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + +/// Converts all macro arguments to pairs of arguments with both string names and values. +#define emp_assert_TO_PAIRS(...) emp_assert_ASSEMBLE(emp_assert_TO_PAIRS, emp_assert_COUNT_ARGS(__VA_ARGS__), __VA_ARGS__) + +#define emp_assert_TO_PAIRS1(X) emp_assert_TO_PAIR(X) +#define emp_assert_TO_PAIRS2(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS1(__VA_ARGS__) +#define emp_assert_TO_PAIRS3(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS2(__VA_ARGS__) +#define emp_assert_TO_PAIRS4(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS3(__VA_ARGS__) +#define emp_assert_TO_PAIRS5(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS4(__VA_ARGS__) +#define emp_assert_TO_PAIRS6(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS5(__VA_ARGS__) +#define emp_assert_TO_PAIRS7(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS6(__VA_ARGS__) +#define emp_assert_TO_PAIRS8(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS7(__VA_ARGS__) +#define emp_assert_TO_PAIRS9(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS8(__VA_ARGS__) +#define emp_assert_TO_PAIRS10(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS9(__VA_ARGS__) + +#define emp_assert_TO_PAIRS11(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS10(__VA_ARGS__) +#define emp_assert_TO_PAIRS12(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS11(__VA_ARGS__) +#define emp_assert_TO_PAIRS13(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS12(__VA_ARGS__) +#define emp_assert_TO_PAIRS14(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS13(__VA_ARGS__) +#define emp_assert_TO_PAIRS15(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS14(__VA_ARGS__) +#define emp_assert_TO_PAIRS16(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS15(__VA_ARGS__) +#define emp_assert_TO_PAIRS17(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS16(__VA_ARGS__) +#define emp_assert_TO_PAIRS18(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS17(__VA_ARGS__) +#define emp_assert_TO_PAIRS19(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS18(__VA_ARGS__) +#define emp_assert_TO_PAIRS20(X, ...) emp_assert_TO_PAIR(X) , emp_assert_TO_PAIRS19(__VA_ARGS__) + +#endif // #ifdef EMP_ASSERT_MACROS_HPP diff --git a/include/emp/base/_native_assert_trigger.hpp b/include/emp/base/_native_assert_trigger.hpp index adc2036a8d..97d586bb0c 100644 --- a/include/emp/base/_native_assert_trigger.hpp +++ b/include/emp/base/_native_assert_trigger.hpp @@ -28,7 +28,14 @@ namespace emp { template void assert_print(std::string name, T && val, EXTRA &&... extra) { if constexpr ( emp::is_streamable::value ) { - std::cerr << name << ": [" << val << "]" << std::endl; + // If we had a literal string fed in, print it as a message. + if (name[0] == '"') { + std::cerr << "MESSAGE: " << val << std::endl; + } + // Otherwise assume that we have a variable and print that. + else { + std::cerr << name << ": [" << val << "]" << std::endl; + } } else std::cerr << name << ": (non-streamable type)" << std::endl; assert_print(std::forward(extra)...); diff --git a/include/emp/base/always_assert.hpp b/include/emp/base/always_assert.hpp index ba04d224da..74109d1aa3 100644 --- a/include/emp/base/always_assert.hpp +++ b/include/emp/base/always_assert.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2020. + * @date 2020-2021. * * @file always_assert.hpp * @brief A more dynamic replacement for standard library asserts. @@ -12,6 +12,7 @@ * - If compiled with Emscripten, will provide pop-up alerts in a web browser. * - emp_assert can take additional arguments. If the assert is triggered, * those extra arguments will be evaluated and printed. + * - If a literal string is provided as an argument, it will be printed as an error message. * - if EMP_TDEBUG is defined, emp_assert() goes into test mode and records * failures, but does not abort. (useful for unit tests of asserts) * @@ -31,45 +32,42 @@ #include #include "_assert_trigger.hpp" -#include "macros.hpp" - -/// Helper macro used throughout... -#define emp_assert_TO_PAIR(X) EMP_STRINGIFY(X) , X +#include "_assert_macros.hpp" #if defined( __EMSCRIPTEN__ ) - #define emp_always_assert_impl(...) \ - do { \ - !(EMP_GET_ARG_1(__VA_ARGS__, ~)) \ - && emp::assert_trigger( \ - __FILE__, __LINE__, \ - EMP_STRINGIFY( EMP_GET_ARG_1(__VA_ARGS__, ~), ), \ - EMP_WRAP_ARGS(emp_assert_TO_PAIR, __VA_ARGS__) \ - ); \ + #define emp_always_assert_impl(...) \ + do { \ + !(emp_assert_GET_ARG_1(__VA_ARGS__, ~)) \ + && emp::assert_trigger( \ + __FILE__, __LINE__, \ + emp_assert_STRINGIFY( emp_assert_GET_ARG_1(__VA_ARGS__, ~), ), \ + emp_assert_TO_PAIRS(__VA_ARGS__) \ + ); \ } while(0) #elif defined( _MSC_VER ) - #define emp_always_assert_msvc_impl(TEST) \ - do { \ - !(TEST) \ - && emp::assert_trigger(__FILE__, __LINE__, #TEST, 0) \ - && (std::abort(), false); \ + #define emp_always_assert_msvc_impl(TEST) \ + do { \ + !(TEST) \ + && emp::assert_trigger(__FILE__, __LINE__, #TEST, 0) \ + && (std::abort(), false); \ } while(0) #define emp_always_assert_impl(TEST) emp_always_assert_msvc_impl(TEST) #else - #define emp_always_assert_impl(...) \ - do { \ - !(EMP_GET_ARG_1(__VA_ARGS__, ~)) \ - && emp::assert_trigger( \ - __FILE__, __LINE__, \ - EMP_STRINGIFY( EMP_GET_ARG_1(__VA_ARGS__, ~) ), \ - EMP_WRAP_ARGS(emp_assert_TO_PAIR, __VA_ARGS__) \ - ) \ - && (std::abort(), false); \ + #define emp_always_assert_impl(...) \ + do { \ + !(emp_assert_GET_ARG_1(__VA_ARGS__, ~)) \ + && emp::assert_trigger( \ + __FILE__, __LINE__, \ + emp_assert_STRINGIFY( emp_assert_GET_ARG_1(__VA_ARGS__, ~) ), \ + emp_assert_TO_PAIRS(__VA_ARGS__) \ + ) \ + && (std::abort(), false); \ } while(0) #endif diff --git a/include/emp/base/always_assert_warning.hpp b/include/emp/base/always_assert_warning.hpp index 23513745c8..773f432f6c 100644 --- a/include/emp/base/always_assert_warning.hpp +++ b/include/emp/base/always_assert_warning.hpp @@ -30,14 +30,11 @@ #include #include "_assert_trigger.hpp" -#include "macros.hpp" - -/// Helper macro used throughout... -#define emp_assert_warning_TO_PAIR(X) EMP_STRINGIFY(X) , X +#include "_assert_macros.hpp" #if defined( _MSC_VER ) - #define emp_always_assert_warning_msvc_impl(TEST) \ + #define emp_always_assert_warning_msvc_impl(TEST) \ do { \ !(TEST) \ && emp::assert_trigger(__FILE__, __LINE__, #TEST, 0); \ @@ -48,13 +45,13 @@ #else - #define emp_always_assert_warning_impl(...) \ + #define emp_always_assert_warning_impl(...) \ do { \ - !(EMP_GET_ARG_1(__VA_ARGS__, ~)) \ + !(emp_assert_GET_ARG_1(__VA_ARGS__, ~)) \ && emp::assert_trigger( \ __FILE__, __LINE__, \ - EMP_STRINGIFY( EMP_GET_ARG_1(__VA_ARGS__, ~) ), \ - EMP_WRAP_ARGS(emp_assert_warning_TO_PAIR, __VA_ARGS__) \ + emp_assert_STRINGIFY( emp_assert_GET_ARG_1(__VA_ARGS__, ~) ), \ + emp_assert_TO_PAIRS(__VA_ARGS__) \ ); \ } while(0) diff --git a/include/emp/base/array.hpp b/include/emp/base/array.hpp index 2d3d111ae1..86904cc706 100644 --- a/include/emp/base/array.hpp +++ b/include/emp/base/array.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2016-2018 + * @date 2016-2021. * * @file array.hpp * @brief A drop-in wrapper for std::array; adds on bounds checking in debug mode. @@ -23,7 +23,6 @@ #include #include "assert.hpp" -#include "../meta/TypeID.hpp" #ifdef EMP_NDEBUG @@ -94,7 +93,7 @@ namespace emp { emp_assert(OK(true, false)); // Ensure array is being pointed to properly. return wrapped_t::operator->(); } - const auto operator->() const { + auto operator->() const { emp_assert(OK(true, false)); // Ensure array is being pointed to properly. return wrapped_t::operator->(); } @@ -163,11 +162,11 @@ namespace emp { // Functions to make sure to throw an error on: - void resize(size_t new_size) { emp_assert(false, "invalid operation for array!"); } - void resize(size_t new_size, const T & val) { emp_assert(false, "invalid operation for array!"); } + void resize(size_t /* new_size */) { emp_assert(false, "invalid operation for array!"); } + void resize(size_t /* new_size */, const T & /* val */) { emp_assert(false, "invalid operation for array!"); } template - void push_back(PB_Ts &&... args) { emp_assert(false, "invalid operation for array!"); } + void push_back(PB_Ts &&... /* args */) { emp_assert(false, "invalid operation for array!"); } void pop_back() { emp_assert(false, "invalid operation for array!"); } template @@ -189,7 +188,7 @@ namespace emp { } template - void emplace_back(ARGS &&... args) { + void emplace_back(ARGS &&... /* args */) { emp_assert(false, "invalid operation for array!"); } }; diff --git a/include/emp/base/optional.hpp b/include/emp/base/optional.hpp index d4c16c7ad9..f2e4588b80 100644 --- a/include/emp/base/optional.hpp +++ b/include/emp/base/optional.hpp @@ -7,9 +7,8 @@ * @brief Audited implementation of std::optional. * @note Status: RELEASE * - * In release mode, functions identically to std::optional. - * In debug mode, operator * and operator-> value accesses are - * checked for undefined behavior. + * Drop-in replacements for std::optional. + * In debug mode, operator * and operator-> value accesses are checked for undefined behavior. */ #ifndef EMP_OPTIONAL_H diff --git a/include/emp/base/vector.hpp b/include/emp/base/vector.hpp index 963508827c..2939ecef0e 100644 --- a/include/emp/base/vector.hpp +++ b/include/emp/base/vector.hpp @@ -31,7 +31,7 @@ // Seemlessly translate emp::vector to std::vector namespace emp { - template using vector = std::vector; + template using vector = std::vector; } @@ -123,7 +123,7 @@ namespace emp { emp_assert(OK(true, false), ErrorCode()); // Ensure vector hasn't changed since making iterator. return wrapped_t::operator->(); } - const auto operator->() const { + auto operator->() const { emp_assert(OK(true, false), ErrorCode()); // Ensure vector hasn't changed since making iterator. return wrapped_t::operator->(); } diff --git a/include/emp/bits/BitArray.hpp b/include/emp/bits/BitArray.hpp new file mode 100644 index 0000000000..65cb5c69ff --- /dev/null +++ b/include/emp/bits/BitArray.hpp @@ -0,0 +1,2093 @@ +/** + * @note This file is part of Empirical, https://github.com/devosoft/Empirical + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file BitArray.hpp + * @brief An Array of a fixed number of bits; similar to std::bitset, but with extra bit magic. + * @note Status: RELEASE + * + * @todo Some of the functions allow a start bit and end bit; each of these should be checked + * to make sure that they will work if the start and end are part of the same byte. One + * option is to do this well ONCE with a macro that properly fills in the details. + */ + + +#ifndef EMP_BIT_ARRAY_HPP +#define EMP_BIT_ARRAY_HPP + +#include +#include +#include +#include + +#include "../base/assert.hpp" +#include "../base/Ptr.hpp" +#include "../base/vector.hpp" +#include "../datastructs/hash_utils.hpp" +#include "../math/math.hpp" +#include "../math/Random.hpp" +#include "../meta/type_traits.hpp" +#include "../polyfill/span.hpp" + +#include "bitset_utils.hpp" +#include "_bitset_helpers.hpp" + +namespace emp { + + /// A fixed-sized (but arbitrarily large) array of bits, and optimizes operations on those bits + /// to be as fast as possible. + /// @param NUM_BITS is the fixed number of bits in this BitArray. + /// @param ZERO_LEFT indicates the side that bit zero will be located. + template + class BitArray { + + // make all templated instantiations friends with each other + template friend class BitArray; + + private: + using this_t = BitArray; + + // Determine the size of the fields to use. By default, size_t will be the natural size for + // the machine; exact fits in other sizes may also allow for skipping zeroing out extra bits. + using field_t = typename emp::uint_bit_count_t; + + // Compile-time constants + static constexpr size_t FIELD_BITS = 8 * sizeof(field_t); + static constexpr size_t NUM_FIELDS = (1 + ((NUM_BITS - 1) / FIELD_BITS)); + static constexpr size_t TOTAL_BYTES = 1 + ((NUM_BITS - 1) >> 3); + static constexpr size_t LAST_FIELD = NUM_FIELDS - 1; + + // Number of bits needed to specify position in a field + mask + static constexpr size_t FIELD_LOG2 = emp::Log2(FIELD_BITS); + static constexpr field_t FIELD_LOG2_MASK = MaskLow(FIELD_LOG2); + + // Track number of bits in the final field; use 0 if a perfect fit. + static constexpr size_t NUM_END_BITS = NUM_BITS & (FIELD_BITS - 1); + + /// How many EXTRA bits are leftover in the gap at the end? + static constexpr size_t END_GAP = NUM_END_BITS ? (FIELD_BITS - NUM_END_BITS) : 0; + + // Mask to use to clear out any end bits that should be zeroes. + static constexpr field_t END_MASK = MaskLow(NUM_END_BITS); + + static constexpr field_t FIELD_0 = (field_t) 0; ///< All bits in a field set to 0 + static constexpr field_t FIELD_1 = (field_t) 1; ///< Least significant bit set to 1 + static constexpr field_t FIELD_255 = (field_t) 255; ///< Least significant 8 bits set to 1 + static constexpr field_t FIELD_ALL = ~FIELD_0; ///< All bits in a field set to 1 + + field_t bits[NUM_FIELDS]; ///< Fields to hold the actual bits for this BitArray. + + // Identify the field that a specified bit is in. + [[nodiscard]] static size_t FieldID(const size_t index) { return index >> FIELD_LOG2; } + + // Identify the byte that a specified bit is in. + [[nodiscard]] static size_t ByteID(const size_t index) { return index >> 3; } + + // Identify the position within a field where a specified bit is. + [[nodiscard]] static size_t FieldPos(const size_t index) { return index & (FIELD_BITS - 1); } + + // Identify the position within a byte where a specified bit is. + [[nodiscard]] static size_t BytePos(const size_t index) { return index & 7; } + + // Identify which field a specified byte position would be in. + [[nodiscard]] static size_t Byte2Field(const size_t index) { return index / sizeof(field_t); } + + // Convert a byte position in BitArray to a byte position in the target field. + [[nodiscard]] static size_t Byte2FieldPos(const size_t index) { return FieldPos(index * 8); } + + // Copy an array of bits into this BitArray (internal use only!) + template + BitArray & Copy(const field_t in_bits[IN_FIELDS]) noexcept { + static_assert(COPY_FIELDS <= IN_FIELDS, "Cannot copy more fields than we are given."); + static_assert(COPY_FIELDS <= NUM_FIELDS, "Cannot copy into more fields than are available."); + constexpr size_t COPY_BYTES = COPY_FIELDS * sizeof(field_t); + std::memcpy(bits, in_bits, COPY_BYTES); + return *this; + } + + // Any bits past the last "real" bit in the last field should be kept as zeros. + void ClearExcessBits() noexcept { if constexpr (NUM_END_BITS > 0) bits[LAST_FIELD] &= END_MASK; } + + // Apply a transformation to each bit field in a specified range. + template + inline BitArray & ApplyRange(const FUN_T & fun, size_t start, size_t stop); + + // Convert the bits to const bytes. + [[nodiscard]] emp::Ptr BytePtr() const + { return reinterpret_cast(bits); } + + // Convert the bits to bytes. + [[nodiscard]] emp::Ptr BytePtr() + { return reinterpret_cast(bits); } + + /// Helper: call SHIFT with positive number instead + void ShiftLeft(const size_t shift_size); + + /// Helper for calling SHIFT with negative number + void ShiftRight(const size_t shift_size); + + /// Helper: call ROTATE with negative number instead + void RotateLeft(const size_t shift_size_raw); + + /// Helper for calling ROTATE with positive number + void RotateRight(const size_t shift_size_raw); + + public: + /// Constructor: Assume all bits set to zero. + explicit BitArray(bool init_val=false) noexcept { if (init_val) SetAll(); else Clear(); } + + /// Copy constructor from another BitArray + BitArray(const this_t & _in) noexcept { Copy(_in.bits); } + + /// Constructor to generate a BitArray from a std::bitset. + explicit BitArray(const std::bitset & bitset); + + /// Constructor to generate a BitArray from a string of '0's and '1's. + BitArray(const std::string & bitstring); + + /// Constructor to generate a BitArray from a literal string of '0's and '1's. + BitArray(const char * bitstring) : BitArray(std::string(bitstring)) { } + + /// Constructor to generate a random BitArray (with equal prob of 0 or 1). + BitArray(Random & random) { Clear(); Randomize(random); } + + /// Constructor to generate a random BitArray with provided PROBABILITY of 1's. + BitArray(Random & random, double p1) { Clear(); Randomize(random, p1); } + + /// Constructor to generate a random BitArray with provided NUMBER of 1's. + BitArray(Random & random, size_t num_ones) { Clear(); ChooseRandom(random, num_ones); } + + /// Constructor to generate a random BitArray with provided NUMBER of 1's. + BitArray(Random & random, int num_ones) { Clear(); ChooseRandom(random, num_ones); } + + /// Constructor to fill in a bit array from a vector. + template BitArray(const std::initializer_list l); + + /// Destructor. + ~BitArray() = default; + + /// Assignment operator (no separate move opperator since no resources to move...) + BitArray & operator=(const this_t & in_bits) noexcept { return Copy(in_bits.bits); } + + /// Assignment operator from a std::bitset. + BitArray & operator=(const std::bitset & bitset); + + /// Assignment operator from a string of '0's and '1's. + BitArray & operator=(const std::string & bitstring); + + /// Assignment operator from a literal string of '0's and '1's. + BitArray & operator=(const char * bitstring) { return operator=(std::string(bitstring)); } + + /// Assignment from another BitArray of a different size. + template + BitArray & Import( const BitArray & from_bits, const size_t from_bit=0 ); + + /// Convert to a BitArray of a different size. + template + BitArray Export(size_t start_bit=0) const; + + /// For debugging: make sure that there are no obvous problems with a BitArray object. + bool OK() const; + + /// How many bits are in this BitArray? + [[nodiscard]] constexpr static size_t GetSize() { return NUM_BITS; } + + /// How many bytes are in this BitArray? + [[nodiscard]] constexpr static size_t GetNumBytes() { return TOTAL_BYTES; } + + /// How many distinct values could be held in this BitArray? + [[nodiscard]] static constexpr double GetNumStates() { return emp::Pow2(NUM_BITS); } + + /// Retrieve the bit as a specified index. + [[nodiscard]] bool Get(size_t index) const; + + /// A safe version of Get() for indexing out of range. Useful for representing collections. + [[nodiscard]] bool Has(size_t index) const { return (index < NUM_BITS) ? Get(index) : false; } + + /// Set the bit at a specified index. + BitArray & Set(size_t index, bool value=true); + + /// Set all bits to one. + BitArray & SetAll() noexcept; + + /// Set a range of bits to one: [start, stop) + BitArray & SetRange(size_t start, size_t stop) + { return ApplyRange([](field_t){ return FIELD_ALL; }, start, stop); } + + /// Set all bits to zero. + BitArray & Clear() noexcept { for (field_t & x : bits) x = FIELD_0; return *this; } + + /// Set specific bit to 0. + BitArray & Clear(size_t index) { return Set(index, false); } + + /// Set bits to 0 in the range [start, stop) + BitArray & Clear(const size_t start, const size_t stop) + { return ApplyRange([](field_t){ return 0; }, start, stop); } + + /// Index into a const BitArray (i.e., cannot be set this way.) + bool operator[](size_t index) const { return Get(index); } + + /// Index into a BitArray, returning a proxy that will allow bit assignment to work. + BitProxy operator[](size_t index) { return BitProxy(*this, index); } + + /// Flip all bits in this BitArray + BitArray & Toggle() { return NOT_SELF(); } + + /// Flip a single bit + BitArray & Toggle(size_t index); + + /// Flips all the bits in a range [start, stop) + BitArray & Toggle(size_t start, size_t stop) + { return ApplyRange([](field_t x){ return ~x; }, start, stop); } + + /// Return true if ANY bits in the BitArray are one, else return false. + [[nodiscard]] bool Any() const { for (auto i : bits) if (i) return true; return false; } + + /// Return true if NO bits in the BitArray are one, else return false. + [[nodiscard]] bool None() const { return !Any(); } + + /// Return true if ALL bits in the BitArray are one, else return false. + [[nodiscard]] bool All() const { return (~(*this)).None(); } + + /// Set all bits randomly, with a 50% probability of being a 0 or 1. + BitArray & Randomize(Random & random); + + /// Set all bits randomly, with probability specified at compile time. + template + BitArray & RandomizeP(Random & random, + const size_t start_pos=0, const size_t stop_pos=NUM_BITS); + + /// Set all bits randomly, with a given probability of being a one. + BitArray & Randomize(Random & random, const double p, + const size_t start_pos=0, const size_t stop_pos=NUM_BITS); + + /// Set all bits randomly, with a fixed number of them being ones. + BitArray & ChooseRandom(Random & random, const size_t target_ones, + const size_t start_pos=0, const size_t stop_pos=NUM_BITS); + + /// Flip random bits with a given probability. + BitArray & FlipRandom(Random & random, const double p, + const size_t start_pos=0, const size_t stop_pos=NUM_BITS); + + /// Set random bits with a given probability (does not check if already set.) + BitArray & SetRandom(Random & random, const double p, + const size_t start_pos=0, const size_t stop_pos=NUM_BITS); + + /// Unset random bits with a given probability (does not check if already zero.) + BitArray & ClearRandom(Random & random, const double p, + const size_t start_pos=0, const size_t stop_pos=NUM_BITS); + + /// Flip a specified number of random bits. + /// @note: This was previously called Mutate. + BitArray & FlipRandomCount(Random & random, const size_t num_bits); + + /// Set a specified number of random bits (does not check if already set.) + BitArray & SetRandomCount(Random & random, const size_t num_bits); + + /// Unset a specified number of random bits (does not check if already zero.) + BitArray & ClearRandomCount(Random & random, const size_t num_bits); + + // >>>>>>>>>> Comparison Operators <<<<<<<<<< // + + template + [[nodiscard]] bool operator==(const BitArray & in) const; + + template + [[nodiscard]] bool operator!=(const BitArray & in) const { return !(*this == in); } + + template + [[nodiscard]] bool operator< (const BitArray & in) const; + + template + [[nodiscard]] bool operator> (const BitArray & in) const { return in < *this; } + + template + [[nodiscard]] bool operator<=(const BitArray & in) const { return !(in < *this); } + + template + [[nodiscard]] bool operator>=(const BitArray & in) const { return !(*this < in); } + + /// Casting a BitArray to bool identifies if ANY bits are set to 1. + explicit operator bool() const { return Any(); } + + + // >>>>>>>>>> Access Groups of bits <<<<<<<<<< // + + /// Retrive the byte at the specified byte index. + [[nodiscard]] uint8_t GetByte(size_t index) const; + + /// Get a read-only view into the internal array used by BitArray. + /// @return Read-only span of BitArray's bytes. + [[nodiscard]] std::span GetBytes() const; + + /// Get a read-only pointer to the internal array used by BitArray. + /// @return Read-only pointer to BitArray's bytes. + [[nodiscard]] emp::Ptr RawBytes() const { return BytePtr(); } + + /// Update the byte at the specified byte index. + void SetByte(size_t index, uint8_t value); + + /// Get the overall value of this BitArray, using a uint encoding, but including all bits + /// and returning the value as a double. + [[nodiscard]] double GetValue() const; + + /// Get specified type at a given index (in steps of that type size) + template [[nodiscard]] T GetValueAtIndex(const size_t index) const; + + /// Retrieve a 'size_t' chunk from the current bits at the specified index. + [[nodiscard]] std::size_t GetSizeT(size_t index) const { return GetValueAtIndex(index); } + + /// Retrieve the 8-bit uint from the specified uint index. + [[nodiscard]] uint8_t GetUInt8(size_t index) const { return GetValueAtIndex(index); } + + /// Retrieve the 16-bit uint from the specified uint index. + [[nodiscard]] uint16_t GetUInt16(size_t index) const { return GetValueAtIndex(index); } + + /// Retrieve the 32-bit uint from the specified uint index. + [[nodiscard]] uint32_t GetUInt32(size_t index) const { return GetValueAtIndex(index); } + + /// Retrieve the 64-bit uint from the specified uint index. + [[nodiscard]] uint64_t GetUInt64(size_t index) const { return GetValueAtIndex(index); } + + /// By default, retrieve the 32-bit uint from the specified uint index. + [[nodiscard]] uint32_t GetUInt(size_t index) const { return GetUInt32(index); } + + + /// Set specified type at a given index (in steps of that type size) + template void SetValueAtIndex(const size_t index, T value); + + /// Update the 8-bit uint at the specified uint index. + void SetUInt8(const size_t index, uint8_t value) { SetValueAtIndex(index, value); } + + /// Update the 16-bit uint at the specified uint index. + void SetUInt16(const size_t index, uint16_t value) { SetValueAtIndex(index, value); } + + /// Update the 32-bit uint at the specified uint index. + void SetUInt32(const size_t index, uint32_t value) { SetValueAtIndex(index, value); } + + /// Update the 64-bit uint at the specified uint index. + void SetUInt64(const size_t index, uint64_t value) { SetValueAtIndex(index, value); } + + /// By default, update the 32-bit uint at the specified uint index. + void SetUInt(const size_t index, uint32_t value) { SetUInt32(index, value); } + + + /// Get specified type starting at a given BIT position. + template [[nodiscard]] T GetValueAtBit(const size_t index) const; + + /// Retrieve the 8-bit uint from the specified uint index. + [[nodiscard]] uint8_t GetUInt8AtBit(size_t index) const { return GetValueAtBit(index); } + + /// Retrieve the 16-bit uint from the specified uint index. + [[nodiscard]] uint16_t GetUInt16AtBit(size_t index) const { return GetValueAtBit(index); } + + /// Retrieve the 32-bit uint from the specified uint index. + [[nodiscard]] uint32_t GetUInt32AtBit(size_t index) const { return GetValueAtBit(index); } + + /// Retrieve the 64-bit uint from the specified uint index. + [[nodiscard]] uint64_t GetUInt64AtBit(size_t index) const { return GetValueAtBit(index); } + + /// By default, retrieve the 32-bit uint from the specified uint index. + [[nodiscard]] uint32_t GetUIntAtBit(size_t index) const { return GetUInt32AtBit(index); } + + + template void SetValueAtBit(const size_t index, T value); + + /// Update the 8-bit uint at the specified uint index. + void SetUInt8AtBit(const size_t index, uint8_t value) { SetValueAtBit(index, value); } + + /// Update the 16-bit uint at the specified uint index. + void SetUInt16AtBit(const size_t index, uint16_t value) { SetValueAtBit(index, value); } + + /// Update the 32-bit uint at the specified uint index. + void SetUInt32AtBit(const size_t index, uint32_t value) { SetValueAtBit(index, value); } + + /// Update the 64-bit uint at the specified uint index. + void SetUInt64AtBit(const size_t index, uint64_t value) { SetValueAtBit(index, value); } + + /// By default, update the 32-bit uint at the specified uint index. + void SetUIntAtBit(const size_t index, uint32_t value) { SetUInt32AtBit(index, value); } + + + // >>>>>>>>>> Other Analyses <<<<<<<<<< // + + /// A simple hash function for bit vectors. + [[nodiscard]] std::size_t Hash() const noexcept; + + /// Count the number of ones in the BitArray. + [[nodiscard]] size_t CountOnes() const; + + /// Faster counting of ones for very sparse bit vectors. + [[nodiscard]] size_t CountOnes_Sparse() const; + + /// Count the number of zeros in the BitArray. + [[nodiscard]] size_t CountZeros() const { return GetSize() - CountOnes(); } + + /// Return the position of the first one; return -1 if no ones in vector. + [[nodiscard]] int FindOne() const; + + /// Deprecated: Return the position of the first one; return -1 if no ones in vector. + [[deprecated("Renamed to more acurate FindOne()")]] + [[nodiscard]] int FindBit() const { return FindOne(); } + + /// Return the position of the first one after start_pos; return -1 if no ones in vector. + /// You can loop through all 1-bit positions of a BitArray "bits" with: + /// + /// for (int pos = bits.FindOne(); pos >= 0; pos = bits.FindOne(pos+1)) { ... } + /// + [[nodiscard]] int FindOne(const size_t start_pos) const; + + /// Deprecated version of FindOne(). + [[deprecated("Renamed to more acurate FindOne(start_pos)")]] + [[nodiscard]] int FindBit(const size_t start_pos) const; + + /// Find the most-significant set-bit. + [[nodiscard]] int FindMaxOne() const; + + /// Return the position of the first one and change it to a zero. Return -1 if no ones. + int PopOne(); + + /// Deprecated version of PopOne(). + [[deprecated("Renamed to more acurate PopOne()")]] + int PopBit() { return PopOne(); } + + /// Return positions of all ones. + [[nodiscard]] emp::vector GetOnes() const; + + /// Find the length of the longest continuous series of ones. + [[nodiscard]] size_t LongestSegmentOnes() const; + + + // >>>>>>>>>> Print/String Functions <<<<<<<<<< // + + /// Convert a specified bit to a character. + [[nodiscard]] char GetAsChar(size_t id) const { return Get(id) ? '1' : '0'; } + + /// Convert this BitArray to a string. + [[nodiscard]] std::string ToString() const; + + /// Convert this BitArray to an array-based string [index 0 on left] + [[nodiscard]] std::string ToArrayString() const; + + /// Convert this BitArray to a numerical string [index 0 on right] + [[nodiscard]] std::string ToBinaryString() const; + + /// Convert this BitArray to a series of IDs + [[nodiscard]] std::string ToIDString(const std::string & spacer=" ") const; + + /// Convert this BitArray to a series of IDs with ranges condensed. + [[nodiscard]] std::string ToRangeString(const std::string & spacer=",", + const std::string & ranger="-") const; + + /// Regular print function (from least significant bit to most) + void Print(std::ostream & out=std::cout) const { out << ToString(); } + + /// Numerical print function (from most significant bit to least) + void PrintBinary(std::ostream & out=std::cout) const { out << ToBinaryString(); } + + /// Print from smallest bit position to largest. + void PrintArray(std::ostream & out=std::cout) const { out << ToString(); } + + /// Print a space between each field (or other provided spacer) + void PrintFields(std::ostream & out=std::cout, const std::string & spacer=" ") const; + + /// Print out details about the internals of the BitArray. + void PrintDebug(std::ostream & out=std::cout) const; + + /// Print the locations of all one bits, using the provided spacer (default is a single space) + void PrintOneIDs(std::ostream & out=std::cout, const std::string & spacer=" ") const; + + /// Print the ones in a range format. E.g., 2-5,7,10-15 + void PrintAsRange(std::ostream & out=std::cout, + const std::string & spacer=",", + const std::string & ranger="-") const; + + + /// Overload ostream operator to return Print. + friend std::ostream& operator<<(std::ostream &out, const BitArray& bs) { + bs.Print(out); + return out; + } + + /// Perform a Boolean NOT on this BitArray, store result here, and return this object. + BitArray & NOT_SELF(); + + /// Perform a Boolean AND with a second BitArray, store result here, and return this object. + BitArray & AND_SELF(const BitArray & array2); + + /// Perform a Boolean OR with a second BitArray, store result here, and return this object. + BitArray & OR_SELF(const BitArray & array2); + + /// Perform a Boolean NAND with a second BitArray, store result here, and return this object. + BitArray & NAND_SELF(const BitArray & array2); + + /// Perform a Boolean NOR with a second BitArray, store result here, and return this object. + BitArray & NOR_SELF(const BitArray & array2); + + /// Perform a Boolean XOR with a second BitArray, store result here, and return this object. + BitArray & XOR_SELF(const BitArray & array2); + + /// Perform a Boolean EQU with a second BitArray, store result here, and return this object. + BitArray & EQU_SELF(const BitArray & array2); + + /// Perform a Boolean NOT on this BitArray and return the result. + [[nodiscard]] BitArray NOT() const { return this_t(*this).NOT_SELF(); } + + /// Perform a Boolean AND with a second BitArray and return the result. + [[nodiscard]] BitArray AND(const BitArray & in) const { return this_t(*this).AND_SELF(in); } + + /// Perform a Boolean OR with a second BitArray and return the result. + [[nodiscard]] BitArray OR(const BitArray & in) const { return this_t(*this).OR_SELF(in); } + + /// Perform a Boolean NAND with a second BitArray and return the result. + [[nodiscard]] BitArray NAND(const BitArray & in) const { return this_t(*this).NAND_SELF(in); } + + /// Perform a Boolean NOR with a second BitArray and return the result. + [[nodiscard]] BitArray NOR(const BitArray & in) const { return this_t(*this).NOR_SELF(in); } + + /// Perform a Boolean XOR with a second BitArray and return the result. + [[nodiscard]] BitArray XOR(const BitArray & in) const { return this_t(*this).XOR_SELF(in); } + + /// Perform a Boolean EQU with a second BitArray and return the result. + BitArray EQU(const BitArray & in) const { return this_t(*this).EQU_SELF(in); } + + /// Positive shifts go right and negative shifts go left (0 does nothing); + /// return result. + [[nodiscard]] BitArray SHIFT(const int shift_size) const; + + /// Positive shifts go right and negative shifts go left (0 does nothing); + /// store result here, and return this object. + BitArray & SHIFT_SELF(const int shift_size); + + /// Reverse the order of bits in the BitArray + BitArray & REVERSE_SELF(); + + /// Reverse order of bits in the BitArray. + [[nodiscard]] BitArray REVERSE() const; + + /// Positive rotates go left and negative rotates go left (0 does nothing); + /// return result. + [[nodiscard]] BitArray ROTATE(const int rotate_size) const; + + /// Positive rotates go right and negative rotates go left (0 does nothing); + /// store result here, and return this object. + BitArray & ROTATE_SELF(const int rotate_size); + + /// Helper: call ROTATE with negative number instead + template + BitArray & ROTL_SELF(); + + /// Helper for calling ROTATE with positive number + template + BitArray & ROTR_SELF(); + + /// Addition of two BitArrays. + /// Wraps if it overflows. + /// Returns result. + [[nodiscard]] BitArray ADD(const BitArray & array2) const; + + /// Addition of two BitArrays. + /// Wraps if it overflows. + /// Returns this object. + BitArray & ADD_SELF(const BitArray & array2); + + /// Subtraction of two BitArrays. + /// Wraps around if it underflows. + /// Returns result. + [[nodiscard]] BitArray SUB(const BitArray & array2) const; + + /// Subtraction of two BitArrays. + /// Wraps if it underflows. + /// Returns this object. + BitArray & SUB_SELF(const BitArray & array2); + + /// Operator bitwise NOT... + [[nodiscard]] BitArray operator~() const { return NOT(); } + + /// Operator bitwise AND... + [[nodiscard]] BitArray operator&(const BitArray & ar2) const { return AND(ar2); } + + /// Operator bitwise OR... + [[nodiscard]] BitArray operator|(const BitArray & ar2) const { return OR(ar2); } + + /// Operator bitwise XOR... + [[nodiscard]] BitArray operator^(const BitArray & ar2) const { return XOR(ar2); } + + /// Operator shift left... + [[nodiscard]] BitArray operator<<(const size_t shift_size) const { return SHIFT(-(int)shift_size); } + + /// Operator shift right... + [[nodiscard]] BitArray operator>>(const size_t shift_size) const { return SHIFT((int)shift_size); } + + /// Compound operator bitwise AND... + BitArray & operator&=(const BitArray & ar2) { return AND_SELF(ar2); } + + /// Compound operator bitwise OR... + BitArray & operator|=(const BitArray & ar2) { return OR_SELF(ar2); } + + /// Compound operator bitwise XOR... + BitArray & operator^=(const BitArray & ar2) { return XOR_SELF(ar2); } + + /// Compound operator shift left... + BitArray & operator<<=(const size_t shift_size) { return SHIFT_SELF(-(int)shift_size); } + + /// Compound operator shift right... + BitArray & operator>>=(const size_t shift_size) { return SHIFT_SELF((int)shift_size); } + + /// Operator plus... + [[nodiscard]] BitArray operator+(const BitArray & ar2) const { return ADD(ar2); } + + /// Operator minus... + [[nodiscard]] BitArray operator-(const BitArray & ar2) const { return SUB(ar2); } + + /// Compound operator plus... + const BitArray & operator+=(const BitArray & ar2) { return ADD_SELF(ar2); } + + /// Compoount operator minus... + const BitArray & operator-=(const BitArray & ar2) { return SUB_SELF(ar2); } + + /// STL COMPATABILITY + /// A set of functions to allow drop-in replacement with std::bitset. + [[nodiscard]] constexpr static size_t size() { return NUM_BITS; } + [[nodiscard]] inline bool all() const { return All(); } + [[nodiscard]] inline bool any() const { return Any(); } + [[nodiscard]] inline bool none() const { return !Any(); } + [[nodiscard]] inline size_t count() const { return CountOnes(); } + inline BitArray & flip() { return Toggle(); } + inline BitArray & flip(size_t pos) { return Toggle(pos); } + inline BitArray & flip(size_t start, size_t stop) { return Toggle(start, stop); } + inline void reset() { Clear(); } + inline void reset(size_t id) { Set(id, false); } + inline void set() { SetAll(); } + inline void set(size_t id) { Set(id); } + [[nodiscard]] inline bool test(size_t index) const { return Get(index); } + + template + void serialize( Archive & ar ) + { + ar( bits ); + } + + }; + + // ------------------------ Implementations for Internal Functions ------------------------ + + template + template + BitArray & + BitArray::ApplyRange(const FUN_T & fun, size_t start, size_t stop) { + if (start == stop) return *this; // Empty range. + + emp_assert(start <= stop, start, stop, NUM_BITS); // Start cannot be after stop. + emp_assert(stop <= NUM_BITS, stop, NUM_BITS); // Stop must be in range. + + const size_t start_pos = FieldPos(start); // Identify the start position WITHIN a bit field. + const size_t stop_pos = FieldPos(stop); // Identify the stop position WITHIN a bit field. + size_t start_field = FieldID(start); // Ideftify WHICH bit field we're starting in. + const size_t stop_field = FieldID(stop-1); // Identify the last field where we actually make a change. + + // If the start field and stop field are the same, mask off the middle. + if (start_field == stop_field) { + const size_t apply_bits = stop - start; // How many bits to change? + const field_t mask = MaskLow(apply_bits) << start_pos; // Target change bits with a mask. + field_t & target = bits[start_field]; // Isolate the field to change. + target = (target & ~mask) | (fun(target) & mask); // Update targeted bits! + } + + // Otherwise mask the ends and fully modify the chunks in between. + else { + // If we're only using a portions of start field, mask it and setup. + if (start_pos != 0) { + const size_t start_bits = FIELD_BITS - start_pos; // How many bits in start field? + const field_t mask = MaskLow(start_bits) << start_pos; // Target start bits with a mask. + field_t & target = bits[start_field]; // Isolate the field to change. + target = (target & ~mask) | (fun(target) & mask); // Update targeted bits! + start_field++; // Done with this field; move to the next. + } + + // Middle fields + for (size_t cur_field = start_field; cur_field < stop_field; cur_field++) { + bits[cur_field] = fun(bits[cur_field]); + } + + // Set portions of stop field + const field_t mask = MaskLow(stop_pos); + field_t & target = bits[stop_field]; // Isolate the field to change. + target = (target & ~mask) | (fun(target) & mask); // Update targeted bits! + } + + return *this; + } + + + template + void BitArray::ShiftLeft(const size_t shift_size) { + // If we have only a single field, this operation can be quick. + if constexpr (NUM_FIELDS == 1) { + bits[0] <<= shift_size; + ClearExcessBits(); + return; + } + + // If we are shifting out of range, clear the bits and stop. + if (shift_size >= NUM_BITS) { Clear(); return; } + + const size_t field_shift = shift_size / FIELD_BITS; + const size_t bit_shift = shift_size % FIELD_BITS; + const size_t bit_overflow = FIELD_BITS - bit_shift; + + // Loop through each field, from L to R, and update it. + if (field_shift) { + for (size_t i = LAST_FIELD; i >= field_shift; --i) { + bits[i] = bits[i - field_shift]; + } + for (size_t i = field_shift; i > 0; i--) bits[i-1] = 0; + } + + // account for bit_shift + if (bit_shift) { + for (size_t i = LAST_FIELD; i > field_shift; --i) { + bits[i] <<= bit_shift; + bits[i] |= (bits[i-1] >> bit_overflow); + } + // Handle final field (field_shift position) + bits[field_shift] <<= bit_shift; + } + + // Mask out any bits that have left-shifted away + ClearExcessBits(); + } + + + /// Helper for calling SHIFT with negative number + template + void BitArray::ShiftRight(const size_t shift_size) { + // If we have only a single field, this operation can be quick. + if constexpr (NUM_FIELDS == 1) { + bits[0] >>= shift_size; + return; + } + + if (!shift_size) return; + + const field_t field_shift = shift_size / FIELD_BITS; + + // Only clear and return if we are field_shift-ing + // We want to be able to always shift by up to a byte so that Import and Export work + if (field_shift && shift_size > NUM_BITS) { + Clear(); + return; + } + const field_t bit_shift = shift_size % FIELD_BITS; + const field_t bit_overflow = FIELD_BITS - bit_shift; + + // account for field_shift + if (field_shift) { + for (size_t i = 0; i < (NUM_FIELDS - field_shift); ++i) { + bits[i] = bits[i + field_shift]; + } + for (size_t i = NUM_FIELDS - field_shift; i < NUM_FIELDS; i++) bits[i] = 0; + } + + // account for bit_shift + if (bit_shift) { + for (size_t i = 0; i < (LAST_FIELD - field_shift); ++i) { + bits[i] >>= bit_shift; + bits[i] |= (bits[i+1] << bit_overflow); + } + bits[LAST_FIELD - field_shift] >>= bit_shift; + } + } + + /// Helper: call ROTATE with negative number + template + void BitArray::RotateLeft(const size_t shift_size_raw) { + const field_t shift_size = shift_size_raw % NUM_BITS; + + // use different approaches based on BitArray size + if constexpr (NUM_FIELDS == 1) { + // special case: for exactly one field_T, try to go low level + // adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c + field_t & n = bits[0]; + size_t c = shift_size; + + // mask necessary to suprress shift count overflow warnings + c &= FIELD_LOG2_MASK; + n = (n<>( (-(c+FIELD_BITS-NUM_BITS)) & FIELD_LOG2_MASK )); + + } else if constexpr (NUM_FIELDS < 32) { + // for small BitArrays, shifting L/R and ORing is faster + this_t dup(*this); + dup.ShiftLeft(shift_size); + ShiftRight(NUM_BITS - shift_size); + OR_SELF(dup); + } else { + // for big BitArrays, manual rotating is fater + + // note that we already modded shift_size by NUM_BITS + // so there's no need to mod by FIELD_SIZE here + const int field_shift = NUM_END_BITS ? ( + (shift_size + FIELD_BITS - NUM_END_BITS) / FIELD_BITS + ) : ( + shift_size / FIELD_BITS + ); + // if we field shift, we need to shift bits by (FIELD_BITS - NUM_END_BITS) + // more to account for the filler that gets pulled out of the middle + const int bit_shift = NUM_END_BITS && field_shift ? ( + (shift_size + FIELD_BITS - NUM_END_BITS) % FIELD_BITS + ) : ( + shift_size % FIELD_BITS + ); + const int bit_overflow = FIELD_BITS - bit_shift; + + // if rotating more than field capacity, we need to rotate fields + std::rotate( + std::rbegin(bits), + std::rbegin(bits)+field_shift, + std::rend(bits) + ); + + // if necessary, shift filler bits out of the middle + if constexpr ((bool)NUM_END_BITS) { + const int filler_idx = (LAST_FIELD + field_shift) % NUM_FIELDS; + for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { + bits[i-1] |= bits[i] << NUM_END_BITS; + bits[i] >>= (FIELD_BITS - NUM_END_BITS); + } + } + + // account for bit_shift + if (bit_shift) { + + const field_t keystone = NUM_END_BITS ? ( + (bits[LAST_FIELD] << (FIELD_BITS - NUM_END_BITS)) + | (bits[NUM_FIELDS - 2] >> NUM_END_BITS) + ) : ( + bits[LAST_FIELD] + ); + + for (int i = LAST_FIELD; i > 0; --i) { + bits[i] <<= bit_shift; + bits[i] |= (bits[i-1] >> bit_overflow); + } + // Handle final field + bits[0] <<= bit_shift; + bits[0] |= keystone >> bit_overflow; + + } + + } + + // Mask out filler bits + ClearExcessBits(); + } + + + /// Helper for calling ROTATE with positive number + template + void BitArray::RotateRight(const size_t shift_size_raw) { + + const size_t shift_size = shift_size_raw % NUM_BITS; + + // use different approaches based on BitArray size + if constexpr (NUM_FIELDS == 1) { + // special case: for exactly one field_t, try to go low level + // adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c + + field_t & n = bits[0]; + size_t c = shift_size; + + // mask necessary to suprress shift count overflow warnings + c &= FIELD_LOG2_MASK; + n = (n>>c) | (n<<( (NUM_BITS-c) & FIELD_LOG2_MASK )); + + } else if constexpr (NUM_FIELDS < 32) { + // for small BitArrays, shifting L/R and ORing is faster + this_t dup(*this); + dup.ShiftRight(shift_size); + ShiftLeft(NUM_BITS - shift_size); + OR_SELF(dup); + } else { + // for big BitArrays, manual rotating is fater + + const field_t field_shift = (shift_size / FIELD_BITS) % NUM_FIELDS; + const int bit_shift = shift_size % FIELD_BITS; + const field_t bit_overflow = FIELD_BITS - bit_shift; + + // if rotating more than field capacity, we need to rotate fields + std::rotate( + std::begin(bits), + std::begin(bits)+field_shift, + std::end(bits) + ); + + // if necessary, shift filler bits out of the middle + if constexpr (NUM_END_BITS > 0) { + const int filler_idx = LAST_FIELD - field_shift; + for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { + bits[i-1] |= bits[i] << NUM_END_BITS; + bits[i] >>= (FIELD_BITS - NUM_END_BITS); + } + } + + // account for bit_shift + if (bit_shift) { + + const field_t keystone = NUM_END_BITS ? ( + bits[0] >> (FIELD_BITS - NUM_END_BITS) + ) : ( + bits[0] + ); + + if constexpr (NUM_END_BITS > 0) { + bits[NUM_FIELDS-1] |= bits[0] << NUM_END_BITS; + } + + for (size_t i = 0; i < LAST_FIELD; ++i) { + bits[i] >>= bit_shift; + bits[i] |= (bits[i+1] << bit_overflow); + } + bits[LAST_FIELD] >>= bit_shift; + bits[LAST_FIELD] |= keystone << bit_overflow; + } + } + + // Mask out filler bits + ClearExcessBits(); + } + + // -------------------- Longer Constructors and bit copying --------------------- + + /// Constructor to generate a BitArray from a std::bitset. + template + BitArray::BitArray(const std::bitset & bitset) { + for (size_t bit{}; bit < NUM_BITS; ++bit) Set( bit, bitset[bit] ); + ClearExcessBits(); + } + + /// Constructor to generate a BitArray from a string of '0's and '1's. + template + BitArray::BitArray(const std::string & bitstring) + { + emp_assert(bitstring.size() <= NUM_BITS); + Clear(); + if constexpr (ZERO_LEFT) { + for (size_t i = 0; i < bitstring.size(); i++) Set(i, bitstring[i] != '0'); + } else { + const size_t in_size = bitstring.size(); + for (size_t i = 0; i < in_size; i++) Set(in_size - i - 1, bitstring[i] != '0'); + } + } + + template + template + BitArray::BitArray(const std::initializer_list l) { + emp_assert(l.size() <= NUM_BITS, "Initializer longer than BitArray", l.size(), NUM_BITS); + Clear(); + if constexpr (ZERO_LEFT) { + auto it = std::begin(l); // Left-most bit is position 0. + for (size_t idx = 0; idx < NUM_BITS; ++idx) Set(idx, (idx < l.size()) && *it++); + } else { + auto it = std::rbegin(l); // Right-most bit is position 0. + for (size_t idx = 0; idx < NUM_BITS; ++idx) Set(idx, (idx < l.size()) && *it++); + } + } + + /// Assignment operator from a std::bitset. + template + BitArray & + BitArray::operator=(const std::bitset & bitset) { + for (size_t i = 0; i < NUM_BITS; i++) Set(i, bitset[i]); + return *this; + } + + /// Assignment operator from a string of '0's and '1's. + template + BitArray & + BitArray::operator=(const std::string & bitstring) { + emp_assert(bitstring.size() <= NUM_BITS); + Clear(); + if constexpr (ZERO_LEFT) { + for (size_t i = 0; i < bitstring.size(); i++) Set(i, bitstring[i] != '0'); + } else { + const size_t in_size = bitstring.size(); + for (size_t i = 0; i < in_size; i++) Set(in_size - i - 1, bitstring[i] != '0'); + } + return *this; + } + + + /// Assign from a BitArray of a different size. + template + template + BitArray & BitArray::Import( + const BitArray & from_array, + const size_t from_bit + ) { + // Only check for same-ness if the two types are the same. + if constexpr (FROM_BITS == NUM_BITS) emp_assert(&from_array != this); + + emp_assert(from_bit < FROM_BITS); + + if (FROM_BITS - from_bit < NUM_BITS) Clear(); + + constexpr size_t DEST_BYTES = (NUM_BITS + 7)/8; + const size_t FROM_BYTES = (FROM_BITS + 7)/8 - from_bit/8; + + const size_t COPY_BYTES = std::min(DEST_BYTES, FROM_BYTES); + + std::memcpy( + bits, + reinterpret_cast(from_array.bits) + from_bit/8, + COPY_BYTES + ); + + if (from_bit%8) { + + this->ShiftRight(from_bit%8); + + if (FROM_BYTES > COPY_BYTES) { + reinterpret_cast(bits)[COPY_BYTES-1] |= ( + reinterpret_cast( + from_array.bits + )[from_bit/8 + COPY_BYTES] + << (8 - from_bit%8) + ); + } + + } + + ClearExcessBits(); + + return *this; + + } + + /// Convert to a BitArray of a different size. + template + template + BitArray BitArray::Export(size_t start_bit) const { + BitArray out_bits; + out_bits.Import(*this, start_bit); + + return out_bits; + } + + /// For debugging: make sure that there are no obvous problems with a BitArray object. + template + bool BitArray::OK() const { + // Make sure final bits are zeroed out. + emp_assert((bits[LAST_FIELD] & ~END_MASK) == 0); + + return true; + } + + + + // -------------------- Implementations of common accessors ------------------- + + template + bool BitArray::Get(size_t index) const { + emp_assert(index >= 0 && index < NUM_BITS); + const size_t field_id = FieldID(index); + const size_t pos_id = FieldPos(index); + return (bits[field_id] & (((field_t)1U) << pos_id)) != 0; + } + + /// Set the bit at a specified index. + template + BitArray & BitArray::Set(size_t index, bool value) { + emp_assert(index < NUM_BITS); + const size_t field_id = FieldID(index); + const size_t pos_id = FieldPos(index); + const field_t pos_mask = FIELD_1 << pos_id; + + if (value) bits[field_id] |= pos_mask; + else bits[field_id] &= ~pos_mask; + + return *this; + } + + /// Set all bits to one. + template + BitArray & BitArray::SetAll() noexcept { + for (field_t & x : bits) x = FIELD_ALL; + ClearExcessBits(); + return *this; + } + + + /// Flip a single bit + template + BitArray & BitArray::Toggle(size_t index) { + emp_assert(index >= 0 && index < NUM_BITS); + const size_t field_id = FieldID(index); + const size_t pos_id = FieldPos(index); + const field_t pos_mask = FIELD_1 << pos_id; + + bits[field_id] ^= pos_mask; + + return *this; + } + + + + // ------------------------- Implementations Randomization functions ------------------------- + + /// Set all bits randomly, with a 50% probability of being a 0 or 1. + template + BitArray & BitArray::Randomize(Random & random) { + random.RandFill(BytePtr(), TOTAL_BYTES); + ClearExcessBits(); + return *this; + } + + /// Set all bits randomly, with probability specified at compile time. + template + template + BitArray & BitArray::RandomizeP(Random & random, + const size_t start_pos, const size_t stop_pos) { + emp_assert(start_pos <= stop_pos); + emp_assert(stop_pos <= NUM_BITS); + random.RandFillP

(BytePtr(), TOTAL_BYTES, start_pos, stop_pos); + ClearExcessBits(); + return *this; + } + + + /// Set all bits randomly, with a given probability of being on. + template + BitArray & BitArray::Randomize(Random & random, const double p, + const size_t start_pos, const size_t stop_pos) { + emp_assert(start_pos <= stop_pos); + emp_assert(stop_pos <= NUM_BITS); + emp_assert(p >= 0.0 && p <= 1.0, p); + random.RandFill(BytePtr(), TOTAL_BYTES, p, start_pos, stop_pos); + ClearExcessBits(); + return *this; + } + + /// Set all bits randomly, with a given number of them being on. + template + BitArray & + BitArray::ChooseRandom( + Random & random, + const size_t target_ones, + const size_t start_pos, + const size_t stop_pos + ) { + emp_assert(start_pos <= stop_pos); + emp_assert(stop_pos <= NUM_BITS); + + const size_t target_size = stop_pos - start_pos; + emp_assert(target_ones <= target_size); + + // Approximate the probability of ones as a starting point. + double p = ((double) target_ones) / (double) target_size; + + // If we are not randomizing the whole sequence, we need to track the number of ones + // in the NON-randomized region to subtract off later. + size_t kept_ones = 0; + if (target_size != NUM_BITS) { + Clear(start_pos, stop_pos); + kept_ones = CountOnes(); + } + + // Try to find a shortcut if p allows.... + // (These values are currently educated guesses) + if (p < 0.12) { if (target_size == NUM_BITS) Clear(start_pos, stop_pos); } + else if (p < 0.2) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.35) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.42) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.58) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.65) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.8) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.88) RandomizeP(random, start_pos, stop_pos); + else SetRange(start_pos, stop_pos); + + size_t cur_ones = CountOnes() - kept_ones; + + // Do we need to add more ones? + while (cur_ones < target_ones) { + size_t pos = random.GetUInt(start_pos, stop_pos); + auto bit = operator[](pos); + if (!bit) { + bit.Set(); + cur_ones++; + } + } + + // See if we have too many ones. + while (cur_ones > target_ones) { + size_t pos = random.GetUInt(start_pos, stop_pos); + auto bit = operator[](pos); + if (bit) { + bit.Clear(); + cur_ones--; + } + } + + return *this; + } + + /// Flip random bits with a given probability. + // @CAO: Possibly faster to generate a sequence of bits and XORing with them. + template + BitArray & BitArray::FlipRandom(Random & random, + const double p, + const size_t start_pos, + const size_t stop_pos) + { + emp_assert(start_pos <= stop_pos); + emp_assert(stop_pos <= NUM_BITS); + emp_assert(p >= 0.0 && p <= 1.0, p); + + for (size_t i=start_pos; i < stop_pos; ++i) if (random.P(p)) Toggle(i); + + return *this; + } + + /// Set random bits with a given probability (does not check if already set.) + template + BitArray & BitArray::SetRandom(Random & random, + const double p, + const size_t start_pos, + const size_t stop_pos) + { + emp_assert(start_pos <= stop_pos); + emp_assert(stop_pos <= NUM_BITS); + emp_assert(p >= 0.0 && p <= 1.0, p); + + for (size_t i=start_pos; i < stop_pos; ++i) if (random.P(p)) Set(i); + + return *this; + } + + /// Unset random bits with a given probability (does not check if already zero.) + template + BitArray & BitArray::ClearRandom(Random & random, + const double p, + const size_t start_pos, + const size_t stop_pos) + { + emp_assert(start_pos <= stop_pos); + emp_assert(stop_pos <= NUM_BITS); + emp_assert(p >= 0.0 && p <= 1.0, p); + + for (size_t i=start_pos; i < stop_pos; ++i) if (random.P(p)) Clear(i); + + return *this; + } + + /// Flip a specified number of random bits. + template + BitArray & BitArray::FlipRandomCount(Random & random, + const size_t num_bits) + { + emp_assert(num_bits <= NUM_BITS); + this_t target_bits(random, num_bits); + return *this ^= target_bits; + } + + /// Set a specified number of random bits (does not check if already set.) + template + BitArray & BitArray::SetRandomCount(Random & random, + const size_t num_bits) + { + emp_assert(num_bits <= NUM_BITS); + this_t target_bits(random, num_bits); + return *this |= target_bits; + } + + /// Unset a specified number of random bits (does not check if already zero.) + template + BitArray & BitArray::ClearRandomCount(Random & random, + const size_t num_bits) + { + emp_assert(num_bits <= NUM_BITS); + this_t target_bits(random, NUM_BITS - num_bits); + return *this &= target_bits; + } + + + // ------------------------- Implementations of Comparison Operators ------------------------- + + /// Test if two BitArray objects are identical. + template + template + bool BitArray::operator==(const BitArray & in_bits) const { + if constexpr (NUM_BITS != SIZE2) return false; + + for (size_t i = 0; i < NUM_FIELDS; ++i) { + if (bits[i] != in_bits.bits[i]) return false; + } + return true; + } + + /// Compare two BitArray objects, based on the associated binary value. + template + template + bool BitArray::operator<(const BitArray & in_bits) const { + if constexpr (NUM_BITS != SIZE2) return NUM_BITS < SIZE2; + + for (int i = NUM_FIELDS-1; i >= 0; --i) { // Start loop at the largest field. + if (bits[i] == in_bits.bits[i]) continue; // If same, keep looking! + return (bits[i] < in_bits.bits[i]); // Otherwise, do comparison + } + return false; + } + + + // ------------------------- Access Groups of bits ------------------------- + + /// Get the full byte starting from the bit at a specified index. + template + uint8_t BitArray::GetByte(size_t index) const { + emp_assert(index < TOTAL_BYTES); + const size_t field_id = Byte2Field(index); + const size_t pos_id = Byte2FieldPos(index); + return (bits[field_id] >> pos_id) & 255; + } + + + /// Get a read-only view into the internal array used by BitArray. + /// @return Read-only span of BitArray's bytes. + template + std::span BitArray::GetBytes() const { + return std::span( + reinterpret_cast(bits), + TOTAL_BYTES + ); + } + + + /// Set the full byte starting at the bit at the specified index. + template + void BitArray::SetByte(size_t index, uint8_t value) { + emp_assert(index < TOTAL_BYTES); + const size_t field_id = Byte2Field(index); + const size_t pos_id = Byte2FieldPos(index); + const field_t val_uint = value; + bits[field_id] = (bits[field_id] & ~(((field_t)255U) << pos_id)) | (val_uint << pos_id); + } + + /// Get the overall value of this BitArray, using a uint encoding, but including all bits + /// and returning the value as a double. + template + double BitArray::GetValue() const { + // If we have 64 bits or fewer, we can load the full value and return it. + if constexpr (NUM_FIELDS == 1) return (double) bits[0]; + + // Otherwise grab the most significant one and figure out how much to shift it by. + const int max_one = FindMaxOne(); + + // If there are no ones, this value must be 0. + if (max_one == -1) return 0.0; + + // If all ones are in the least-significant field, just return it. + // NOTE: If we have more than one field, FIELD_SIZE is usually 64 already. + if (max_one < 64) return (double) GetUInt64(0); + + // To grab the most significant field, figure out how much to shift it by. + const size_t shift_bits = (size_t) max_one - 63; + double out_value = (double) (*this >> shift_bits).GetUInt64(0); + + out_value *= emp::Pow2(shift_bits); + + return out_value; + } + + /// Get specified type at a given index (in steps of that type size) + template + template + T BitArray::GetValueAtIndex(const size_t index) const { + // For the moment, must fit inside bounds; eventually should pad with zeros. + emp_assert((index + 1) * sizeof(T) <= NUM_FIELDS * sizeof(field_t), + index, sizeof(T), NUM_BITS, NUM_FIELDS); + + // If we are using the native field type, just grab it from bits. + if constexpr( std::is_same() ) return bits[index]; + + T out_value; + std::memcpy( &out_value, BytePtr().Raw() + index * sizeof(T), sizeof(T) ); + return out_value; + } + + + /// Set specified type at a given index (in steps of that type size) + template + template + void BitArray::SetValueAtIndex(const size_t index, T in_value) { + // For the moment, must fit inside bounds; eventually should pad with zeros. + emp_assert((index + 1) * sizeof(T) <= NUM_FIELDS * sizeof(field_t), + index, sizeof(T), NUM_BITS, NUM_FIELDS); + + std::memcpy( BytePtr().Raw() + index * sizeof(T), &in_value, sizeof(T) ); + + ClearExcessBits(); + } + + + /// Get the specified type starting from a given BIT position. + template + template + T BitArray::GetValueAtBit(const size_t index) const { + // For the moment, must fit inside bounds; eventually should pad with zeros. + emp_assert((index+7)/8 + sizeof(T) < NUM_FIELDS * sizeof(field_t)); + + BitArray out_bits; + out_bits.Import(*this, index); + + return out_bits.template GetValueAtIndex(0); + } + + + /// Set the specified type starting from a given BIT position. + // @CAO: Can be optimized substantially, especially for long BitArrays. + template + template + void BitArray::SetValueAtBit(const size_t index, T value) { + // For the moment, must fit inside bounds; eventually should pad with zeros. + emp_assert((index+7)/8 + sizeof(T) < NUM_FIELDS * sizeof(field_t)); + constexpr size_t type_bits = sizeof(T) * 8; + + Clear(index, index+type_bits); // Clear out the bits where new value will go. + this_t in_bits; // Setup a BitArray to place the new bits in. + in_bits.SetValueAtIndex(0, value); // Insert the new bits. + in_bits <<= index; // Shift new bits into place. + OR_SELF(in_bits); // Place new bits into current BitArray. + + ClearExcessBits(); + } + + + // ------------------------- Other Analyses ------------------------- + + /// A simple hash function for bit vectors. + template + std::size_t BitArray::Hash() const noexcept { + /// If we have a vector of size_t, treat it as a vector of hash values to combine. + if constexpr (std::is_same_v) { + return hash_combine(bits, NUM_FIELDS); + } + + constexpr size_t SIZE_T_BITS = sizeof(std::size_t)*8; + + // If all of the bits will fit into a single size_t, return it. + if constexpr (NUM_BITS <= SIZE_T_BITS) return GetSizeT(0); + + // If the bits fit into TWO size_t units, merge them. + if constexpr (NUM_BITS <= 2 * SIZE_T_BITS) { + return emp::hash_combine(GetSizeT(0), GetSizeT(1)); + } + + // Otherwise just use murmur hash (should never happen and slightly slower, but generalizes). + return emp::murmur_hash( GetBytes() ); + } + + // TODO: see https://arxiv.org/pdf/1611.07612.pdf for fast pop counts + /// Count the number of ones in the BitArray. + template + size_t BitArray::CountOnes() const { + size_t bit_count = 0; + for (size_t i = 0; i < NUM_FIELDS; ++i) { + // when compiling with -O3 and -msse4.2, this is the fastest population count method. + std::bitset std_bs(bits[i]); + bit_count += std_bs.count(); + } + + return bit_count; + } + + /// Faster counting of ones for very sparse bit vectors. + template + size_t BitArray::CountOnes_Sparse() const { + size_t bit_count = 0; + for (field_t cur_field : bits) { + while (cur_field) { + cur_field &= (cur_field-1); // Peel off a single 1. + bit_count++; // Increment the counter + } + } + return bit_count; + } + + /// Return the index of the first one in the sequence; return -1 if no ones are available. + template + int BitArray::FindOne() const { + size_t field_id = 0; + while (field_id < NUM_FIELDS && bits[field_id]==0) field_id++; + return (field_id < NUM_FIELDS) ? + (int) (find_bit(bits[field_id]) + (field_id << FIELD_LOG2)) : -1; + } + + /// Return index of first one in sequence AFTER start_pos (or -1 if no ones) + template + int BitArray::FindOne(const size_t start_pos) const { + if (start_pos >= NUM_BITS) return -1; // If we're past the end, return fail. + size_t field_id = FieldID(start_pos); // What field do we start in? + const size_t field_pos = FieldPos(start_pos); // What position in that field? + + // If there's a hit in a partial first field, return it. + if (field_pos && (bits[field_id] & ~(MaskLow(field_pos)))) { + return (int) (find_bit(bits[field_id] & ~(MaskLow(field_pos))) + + field_id * FIELD_BITS); + } + + // Search other fields... + if (field_pos) field_id++; + while (field_id < NUM_FIELDS && bits[field_id]==0) field_id++; + return (field_id < NUM_FIELDS) ? + (int) (find_bit(bits[field_id]) + (field_id * FIELD_BITS)) : -1; + } + + /// Find the most-significant set-bit. + template + int BitArray::FindMaxOne() const { + // Find the max field with a one. + int max_field = ((int) NUM_FIELDS) - 1; + while (max_field >= 0 && bits[max_field] == 0) max_field--; + + // If there are no ones, return -1. + if (max_field == -1) return -1; + + const field_t field = bits[max_field]; // Save a local copy of this field. + field_t mask = (field_t) -1; // Mask off the bits still under consideration. + size_t offset = 0; // Indicate where the mask should be applied. + size_t range = FIELD_BITS; // Indicate how many bits are in the mask. + + while (range > 1) { + // Cut the range in half and see if we need to adjust the offset. + range /= 2; // Cut range size in half + mask >>= range; // Cut the mask down. + + // Check the upper half of original range; if has a one shift new offset to there. + if (field & (mask << (offset + range))) offset += range; + } + + return (int) (max_field * FIELD_BITS + offset); + } + + /// Return index of first one in sequence (or -1 if no ones); change this position to zero. + template + int BitArray::PopOne() { + const int out_bit = FindOne(); + if (out_bit >= 0) Clear(out_bit); + return out_bit; + } + + /// Return a vector indicating the posistions of all ones in the BitArray. + template + emp::vector BitArray::GetOnes() const { + // @CAO -- There are better ways to do this with bit tricks. + emp::vector ones(CountOnes()); + size_t cur_pos = 0; + for (size_t i = 0; i < NUM_BITS; i++) { + if (Get(i)) ones[cur_pos++] = i; + } + return ones; + } + + /// Find the length of the longest continuous series of ones. + template + size_t BitArray::LongestSegmentOnes() const { + size_t length = 0; + BitArray test_bits(*this); + while(test_bits.Any()){ + ++length; + test_bits.AND_SELF(test_bits<<1); + } + return length; + } + + + // ------------------------- Print/String Functions ------------------------- // + + /// Convert this BitArray to a string (using default direction) + template + std::string BitArray::ToString() const { + if constexpr (ZERO_LEFT) return ToArrayString(); + else return ToBinaryString(); + } + + /// Convert this BitArray to an array string [0 index on left] + template + std::string BitArray::ToArrayString() const { + std::string out_string; + out_string.reserve(NUM_BITS); + for (size_t i = 0; i < NUM_BITS; ++i) out_string.push_back(GetAsChar(i)); + return out_string; + } + + /// Convert this BitArray to a numerical string [0 index on right] + template + std::string BitArray::ToBinaryString() const { + std::string out_string; + out_string.reserve(NUM_BITS); + for (size_t i = NUM_BITS; i > 0; --i) out_string.push_back(GetAsChar(i-1)); + return out_string; + } + + /// Convert this BitArray to a series of IDs + template + std::string BitArray::ToIDString(const std::string & spacer) const { + std::stringstream ss; + PrintOneIDs(ss, spacer); + return ss.str(); + } + + /// Convert this BitArray to a series of IDs with ranges condensed. + template + std::string BitArray::ToRangeString(const std::string & spacer, + const std::string & ranger) const + { + std::stringstream ss; + PrintAsRange(ss, spacer, ranger); + return ss.str(); + } + + /// Print a space between each field (or other provided spacer) + template + void BitArray::PrintFields(std::ostream & out, const std::string & spacer) const { + for (size_t i = NUM_BITS-1; i < NUM_BITS; i--) { + out << Get(i); + if (i && (i % FIELD_BITS == 0)) out << spacer; + } + } + + /// Print a space between each field (or other provided spacer) + template + void BitArray::PrintDebug(std::ostream & out) const { + for (size_t field = 0; field < NUM_FIELDS; field++) { + for (size_t bit_id = 0; bit_id < FIELD_BITS; bit_id++) { + bool bit = (FIELD_1 << bit_id) & bits[field]; + out << ( bit ? 1 : 0 ); + } + out << " : " << field << std::endl; + } + size_t end_pos = NUM_END_BITS; + if (end_pos == 0) end_pos = FIELD_BITS; + for (size_t i = 0; i < end_pos; i++) out << " "; + out << "^" << std::endl; + } + + /// Print the locations of all one bits, using the provided spacer (default is a single space) + template + void BitArray::PrintOneIDs(std::ostream & out, const std::string & spacer) const { + bool started = false; + for (size_t i = 0; i < NUM_BITS; i++) { + if (Get(i)) { + if (started) out << spacer; + out << i; + started = true; + } + } + } + + /// Print the ones in a range format. E.g., 2-5,7,10-15 + template + void BitArray::PrintAsRange(std::ostream & out, + const std::string & spacer, + const std::string & ranger) const + { + emp::vector ones = GetOnes(); // Identify the one to represent in output. + + for (size_t pos = 0; pos < ones.size(); pos++) { + if (pos) out << spacer; // If not first range, put a space before it. + size_t start = ones[pos]; // The current range starts here. + while (pos+1 < ones.size() && // If there is another one... + ones[pos+1] == ones[pos]+1) pos++; // ...and it is sequential to this one, grab it. + size_t end = ones[pos]; // The last one we got to is the end position. + + out << start; // Output the range start. + if (start != end) out << ranger << end; // If there's more than one in range, show range. + } + } + + + // ------------------------- Whole BitArray manipulation functions ------------------------- + + + /// Perform a Boolean NOT on this BitArray, store result here, and return this object. + template + BitArray & BitArray::NOT_SELF() { + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = ~bits[i]; + ClearExcessBits(); + return *this; + } + + /// Perform a Boolean AND with a second BitArray, store result here, and return this object. + template + BitArray & BitArray::AND_SELF(const this_t & array2) { + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = bits[i] & array2.bits[i]; + return *this; + } + + /// Perform a Boolean OR with a second BitArray, store result here, and return this object. + template + BitArray & BitArray::OR_SELF(const this_t & array2) { + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = bits[i] | array2.bits[i]; + return *this; + } + + /// Perform a Boolean NAND with a second BitArray, store result here, and return this object. + template + BitArray & BitArray::NAND_SELF(const this_t & array2) { + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = ~(bits[i] & array2.bits[i]); + ClearExcessBits(); + return *this; + } + + /// Perform a Boolean NOR with a second BitArray, store result here, and return this object. + template + BitArray & BitArray::NOR_SELF(const this_t & array2) { + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = ~(bits[i] | array2.bits[i]); + ClearExcessBits(); + return *this; + } + + /// Perform a Boolean XOR with a second BitArray, store result here, and return this object. + template + BitArray & BitArray::XOR_SELF(const this_t & array2) { + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = bits[i] ^ array2.bits[i]; + return *this; + } + + /// Perform a Boolean EQU with a second BitArray, store result here, and return this object. + template + BitArray & BitArray::EQU_SELF(const this_t & array2) { + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = ~(bits[i] ^ array2.bits[i]); + ClearExcessBits(); + return *this; + } + + /// Positive shifts go right and negative shifts go left (0 does nothing); + /// return result. + template + BitArray BitArray::SHIFT(const int shift_size) const { + this_t out_array(*this); + if (shift_size > 0) out_array.ShiftRight((field_t) shift_size); + else if (shift_size < 0) out_array.ShiftLeft((field_t) (-shift_size)); + return out_array; + } + + /// Positive shifts go right and negative shifts go left (0 does nothing); + /// store result here, and return this object. + template + BitArray & BitArray::SHIFT_SELF(const int shift_size) { + if (shift_size > 0) ShiftRight((field_t) shift_size); + else if (shift_size < 0) ShiftLeft((field_t) -shift_size); + return *this; + } + + /// Reverse the order of bits in the BitArray + template + BitArray & BitArray::REVERSE_SELF() { + + // reverse bytes + std::reverse( BytePtr().Raw(), BytePtr().Raw() + TOTAL_BYTES ); + + // reverse each byte + // adapted from https://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte + for (size_t i = 0; i < TOTAL_BYTES; ++i) { + unsigned char & b = BytePtr()[i]; + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + } + + // shift out filler bits + constexpr size_t filler_bits = NUM_BITS % 8; + if constexpr (filler_bits != 0) { + this->ShiftRight(8-filler_bits); + } + + return *this; + + } + + /// Reverse order of bits in the BitArray. + template + BitArray BitArray::REVERSE() const { + this_t out_array(*this); + return out_array.REVERSE_SELF(); + } + + + /// Positive rotates go left and negative rotates go left (0 does nothing); + /// return result. + template + BitArray BitArray::ROTATE(const int rotate_size) const { + this_t out_array(*this); + if (rotate_size > 0) out_array.RotateRight((field_t) rotate_size); + else if (rotate_size < 0) out_array.RotateLeft((field_t) (-rotate_size)); + return out_array; + } + + /// Positive rotates go right and negative rotates go left (0 does nothing); + /// store result here, and return this object. + template + BitArray & BitArray::ROTATE_SELF(const int rotate_size) { + if (rotate_size > 0) RotateRight((field_t) rotate_size); + else if (rotate_size < 0) RotateLeft((field_t) -rotate_size); + return *this; + } + + /// Helper: call ROTATE with negative number instead + template + template + BitArray & BitArray::ROTL_SELF() { + constexpr size_t shift_size = shift_size_raw % NUM_BITS; + + // special case: for exactly one field_t, try to go low level + // adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c + if constexpr (NUM_FIELDS == 1) { + field_t & n = bits[0]; + size_t c = shift_size; + + // mask necessary to suprress shift count overflow warnings + c &= FIELD_LOG2_MASK; + n = (n<>( (-(c+FIELD_BITS-NUM_BITS)) & FIELD_LOG2_MASK )); + + } else { + + // note that we already modded shift_size by NUM_BITS + // so there's no need to mod by FIELD_SIZE here + constexpr int field_shift = NUM_END_BITS ? ( + (shift_size + FIELD_BITS - NUM_END_BITS) / FIELD_BITS + ) : ( + shift_size / FIELD_BITS + ); + // if we field shift, we need to shift bits by (FIELD_BITS - NUM_END_BITS) + // more to account for the filler that gets pulled out of the middle + constexpr int bit_shift = NUM_END_BITS && field_shift ? ( + (shift_size + FIELD_BITS - NUM_END_BITS) % FIELD_BITS + ) : ( + shift_size % FIELD_BITS + ); + constexpr int bit_overflow = FIELD_BITS - bit_shift; + + // if rotating more than field capacity, we need to rotate fields + if constexpr ((bool)field_shift) { + std::rotate( + std::rbegin(bits), + std::rbegin(bits)+field_shift, + std::rend(bits) + ); + } + + // if necessary, shift filler bits out of the middle + if constexpr ((bool)NUM_END_BITS) { + const int filler_idx = (LAST_FIELD + field_shift) % NUM_FIELDS; + for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { + bits[i-1] |= bits[i] << NUM_END_BITS; + bits[i] >>= (FIELD_BITS - NUM_END_BITS); + } + } + + // account for bit_shift + if (bit_shift) { + + const field_t keystone = NUM_END_BITS ? ( + (bits[LAST_FIELD] << (FIELD_BITS - NUM_END_BITS)) + | (bits[NUM_FIELDS - 2] >> NUM_END_BITS) + ) : ( + bits[LAST_FIELD] + ); + + for (int i = LAST_FIELD; i > 0; --i) { + bits[i] <<= bit_shift; + bits[i] |= (bits[i-1] >> bit_overflow); + } + // Handle final field + bits[0] <<= bit_shift; + bits[0] |= keystone >> bit_overflow; + + } + + } + + ClearExcessBits(); + + return *this; + + } + + + /// Helper for calling ROTATE with positive number + template + template + BitArray & BitArray::ROTR_SELF() { + + constexpr size_t shift_size = shift_size_raw % NUM_BITS; + + // special case: for exactly one field_t, try to go low level + // adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c + if constexpr (NUM_FIELDS == 1) { + field_t & n = bits[0]; + size_t c = shift_size; + + // mask necessary to suprress shift count overflow warnings + c &= FIELD_LOG2_MASK; + n = (n>>c) | (n<<( (NUM_BITS-c) & FIELD_LOG2_MASK )); + + } else { + + constexpr field_t field_shift = (shift_size / FIELD_BITS) % NUM_FIELDS; + constexpr int bit_shift = shift_size % FIELD_BITS; + constexpr field_t bit_overflow = FIELD_BITS - bit_shift; + + // if rotating more than field capacity, we need to rotate fields + if constexpr ((bool)field_shift) { + std::rotate( + std::begin(bits), + std::begin(bits)+field_shift, + std::end(bits) + ); + } + + // if necessary, shift filler bits out of the middle + if constexpr ((bool)NUM_END_BITS) { + constexpr int filler_idx = LAST_FIELD - field_shift; + for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { + bits[i-1] |= bits[i] << NUM_END_BITS; + bits[i] >>= (FIELD_BITS - NUM_END_BITS); + } + } + + // account for bit_shift + if (bit_shift) { + + const field_t keystone = NUM_END_BITS ? ( + bits[0] >> (FIELD_BITS - NUM_END_BITS) + ) : ( + bits[0] + ); + + if constexpr ((bool)NUM_END_BITS) { + bits[NUM_FIELDS-1] |= bits[0] << NUM_END_BITS; + } + + for (size_t i = 0; i < LAST_FIELD; ++i) { + bits[i] >>= bit_shift; + bits[i] |= (bits[i+1] << bit_overflow); + } + bits[LAST_FIELD] >>= bit_shift; + bits[LAST_FIELD] |= keystone << bit_overflow; + } + } + + ClearExcessBits(); + + return *this; + + } + + /// Addition of two BitArrays. + /// Wraps if it overflows. + /// Returns result. + template + BitArray BitArray::ADD(const BitArray & array2) const{ + this_t out_array(*this); + return out_array.ADD_SELF(array2); + } + + /// Addition of two BitArrays. + /// Wraps if it overflows. + /// Returns this object. + template + BitArray & BitArray::ADD_SELF(const this_t & array2) { + bool carry = false; + + for (size_t i = 0; i < NUM_BITS/FIELD_BITS; ++i) { + field_t addend = array2.bits[i] + static_cast(carry); + carry = array2.bits[i] > addend; + + field_t sum = bits[i] + addend; + carry |= bits[i] > sum; + + bits[i] = sum; + } + + if constexpr (static_cast(NUM_END_BITS)) { + bits[NUM_BITS/FIELD_BITS] = ( + bits[NUM_BITS/FIELD_BITS] + + array2.bits[NUM_BITS/FIELD_BITS] + + static_cast(carry) + ) & END_MASK; + } + + return *this; + } + + /// Subtraction of two BitArrays. + /// Wraps around if it underflows. + /// Returns result. + template + BitArray BitArray::SUB(const this_t & array2) const{ + this_t out_array(*this); + return out_array.SUB_SELF(array2); + } + + /// Subtraction of two BitArrays. + /// Wraps if it underflows. + /// Returns this object. + template + BitArray & BitArray::SUB_SELF(const this_t & array2){ + + bool carry = false; + + for (size_t i = 0; i < NUM_BITS/FIELD_BITS; ++i) { + const field_t subtrahend = array2.bits[i] + static_cast(carry); + carry = array2.bits[i] > subtrahend; + carry |= bits[i] < subtrahend; + bits[i] -= subtrahend; + } + + if constexpr (static_cast(NUM_END_BITS)) { + bits[NUM_BITS/FIELD_BITS] = ( + bits[NUM_BITS/FIELD_BITS] + - array2.bits[NUM_BITS/FIELD_BITS] + - static_cast(carry) + ) & END_MASK; + } + + return *this; + } + + + // ------------------------- Extra Functions ------------------------- + + template + BitArray join(const BitArray & in1, const BitArray & in2) { + BitArray out_bits; + out_bits.Import(in2); + out_bits <<= NUM_BITS1; + out_bits |= in1.template Export(); + } + + /// Computes simple matching coefficient (https://en.wikipedia.org/wiki/Simple_matching_coefficient). + template + double SimpleMatchCoeff(const BitArray & in1, + const BitArray & in2) { + emp_assert(NUM_BITS > 0); // TODO: can be done with XOR + return (double)(~(in1 ^ in2)).CountOnes() / (double) NUM_BITS; + } + +} + +/// For hashing BitArrays +namespace std +{ + template + struct hash> + { + size_t operator()( const emp::BitArray & bs ) const noexcept + { + return bs.Hash(); + } + }; +} + +#endif diff --git a/include/emp/bits/BitMatrix.hpp b/include/emp/bits/BitMatrix.hpp index f26fc4e6fc..758ed41c75 100644 --- a/include/emp/bits/BitMatrix.hpp +++ b/include/emp/bits/BitMatrix.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2016-2017 + * @date 2016-2021 * * @file BitMatrix.hpp * @brief A COL x ROW matrix of bits and provides easy indexing and manipulation @@ -115,9 +115,9 @@ namespace emp { size_t CountOnes() const { return bits.count(); } // Find the position of the first non-zero bit. - // size_t FindBit() const { return (~bits & (bits - 1)).count(); } + // size_t FindOne() const { return (~bits & (bits - 1)).count(); } - int FindBit() const { return bits.FindBit(); } + int FindOne() const { return bits.FindOne(); } // Shift the whole matrix in the specified direction. BitMatrix LeftShift() const { return ((bits & ~MaskCol<0>()) >> 1); } @@ -150,7 +150,7 @@ namespace emp { BitMatrix GetRegion(size_t col, size_t row) const { return GetRegion(ToID(col,row)); } // Does this bit matrix represent a connected set of ones? - bool IsConnected() const { return GetRegion((size_t)FindBit()) == *this; } + bool IsConnected() const { return GetRegion((size_t)FindOne()) == *this; } // Does this bit matrix have any 2x2 square of ones in it? bool Has2x2() const { return (*this & UpShift() & LeftShift() & ULShift()).Any(); } diff --git a/include/emp/bits/BitSet.hpp b/include/emp/bits/BitSet.hpp index 3c1f800025..5b34b4740e 100644 --- a/include/emp/bits/BitSet.hpp +++ b/include/emp/bits/BitSet.hpp @@ -1,1432 +1,25 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2016-2018 + * @date 2016-2021. * * @file BitSet.hpp - * @brief A drop-in replacement for std::bitset, with additional bit magic features. + * @brief A drop-in replacement for std::bitset, with additional bit magic features; aliases BitArray. * @note Status: RELEASE * - * @note Like std::bitset, bit zero is on the right side. Unlike std::bitset, emp::BitSet - * gives access to bit fields for easy access to different sized chucnk of bits and - * implementation new bit-magic tricks. */ #ifndef EMP_BIT_SET_HPP #define EMP_BIT_SET_HPP -#include -#include -#include -#include - -#include "../base/assert.hpp" -#include "../base/vector.hpp" -#include "../base/Ptr.hpp" -#include "../datastructs/hash_utils.hpp" -#include "../math/math.hpp" -#include "../math/Random.hpp" -#include "../math/random_utils.hpp" -#include "../polyfill/span.hpp" -#include "bitset_utils.hpp" +#include "BitArray.hpp" namespace emp { - /// SFINAE helper to determine field_t for BitSet - template struct FieldHelper { -#ifdef __EMSCRIPTEN__ - ///< Field sizes are 32 bits in Emscripten (max directly handled) - using field_t = uint32_t; -#else - ///< Field sizes are 64 bits in native, unless NUM_BITS == 32 - using field_t = uint64_t; -#endif - }; - - template <> struct FieldHelper<32> { - // if NUM_BITS == 32, use uint32_t - using field_t = uint32_t; - }; - - /// A fixed-sized (but arbitrarily large) array of bits, and optimizes operations on those bits - /// to be as fast as possible. - template class BitSet { - - // make all templated instantiations friends with each other - template friend class BitSet; - - private: - - ///< field size is 64 for native (except NUM_BITS == 32), 32 for emscripten - using field_t = typename FieldHelper::field_t; - - ///< How many bytes are in a field? - static constexpr field_t FIELD_BYTES = sizeof(field_t); - - ///< How many bits are in a field? - static constexpr field_t FIELD_BITS = 8 * FIELD_BYTES; - - static constexpr field_t FIELD_LOG2 = emp::Log2(FIELD_BITS); - - /// Fields hold bits in groups of 32 or 64 (as uint32_t or uint64_t); - /// how many fields do we need? - static constexpr field_t NUM_FIELDS = (1 + ((NUM_BITS - 1) / FIELD_BITS)); - - /// End position of the stored bits in the last field; 0 if perfect fit. - static constexpr field_t LAST_BIT = NUM_BITS & (FIELD_BITS - 1); - - /// How many total bytes are needed to represent these bits? (rounded up to full bytes) - static const field_t NUM_BYTES = 1 + ((NUM_BITS - 1) >> 3); - - field_t bit_set[NUM_FIELDS]; ///< Fields to hold the actual bits for this BitSet. - - /// BitProxy lets us use operator[] on with BitSet as an lvalue. - class BitProxy { - private: - BitSet & bit_set; ///< BitSet object that this proxy refers to. - size_t index; ///< Position in BitSet the this proxy refers to. - public: - BitProxy(BitSet & _set, size_t _idx) : bit_set(_set), index(_idx) { - emp_assert(_idx < bit_set.size()); - } - - /// Set the bit value that this proxy refers to. - BitProxy & operator=(bool b) { // lvalue handling... - bit_set.Set(index, b); - return *this; - } - - /// Convert BitProxy to a regular boolean value. - operator bool() const { // rvalue handling... - return bit_set.Get(index); - } - - /// Flip this bit. - BitProxy & Toggle() { bit_set.Toggle(index); return *this; } - }; - friend class BitProxy; - - inline static size_t FieldID(const size_t index) { - emp_assert((index >> FIELD_LOG2) < NUM_FIELDS); - return index >> FIELD_LOG2; - } - - inline static size_t FieldPos(const size_t index) { - return index & (FIELD_BITS - 1); - } - - inline static size_t Byte2Field(const size_t index) { - return index / FIELD_BYTES; - } - - inline static size_t Byte2FieldPos(const size_t index) { - return FieldPos(index * 8); - } - - inline void Copy(const field_t in_set[NUM_FIELDS]) { - std::memcpy(bit_set, in_set, sizeof(bit_set)); - } - - /// Helper: call SHIFT with positive number instead - void ShiftLeft(const size_t shift_size) { - - if (shift_size > NUM_BITS) { - Clear(); - return; - } - - const int field_shift = shift_size / FIELD_BITS; - const int bit_shift = shift_size % FIELD_BITS; - const int bit_overflow = FIELD_BITS - bit_shift; - - // Loop through each field, from L to R, and update it. - if (field_shift) { - for (int i = NUM_FIELDS - 1; i >= field_shift; --i) { - bit_set[i] = bit_set[i - field_shift]; - } - for (int i = field_shift - 1; i >= 0; i--) bit_set[i] = 0; - } - - // account for bit_shift - if (bit_shift) { - for (int i = NUM_FIELDS - 1; i > field_shift; --i) { - bit_set[i] <<= bit_shift; - bit_set[i] |= (bit_set[i-1] >> bit_overflow); - } - // Handle final field (field_shift position) - bit_set[field_shift] <<= bit_shift; - } - - // Mask out any bits that have left-shifted away - if (LAST_BIT) { bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); } - } - - - /// Helper for calling SHIFT with negative number - void ShiftRight(const size_t shift_size) { - if (!shift_size) return; - - const field_t field_shift = shift_size / FIELD_BITS; - - // only clear and return if we are field_shift-ing - // we want to be able to always shift by up to a byte - // so that Import and Export work - if (field_shift && shift_size > NUM_BITS) { - Clear(); - return; - } - const field_t bit_shift = shift_size % FIELD_BITS; - const field_t bit_overflow = FIELD_BITS - bit_shift; - - // account for field_shift - if (field_shift) { - for (size_t i = 0; i < (NUM_FIELDS - field_shift); ++i) { - bit_set[i] = bit_set[i + field_shift]; - } - for (size_t i = NUM_FIELDS - field_shift; i < NUM_FIELDS; i++) bit_set[i] = 0; - } - - // account for bit_shift - if (bit_shift) { - for (size_t i = 0; i < (NUM_FIELDS - 1 - field_shift); ++i) { - bit_set[i] >>= bit_shift; - bit_set[i] |= (bit_set[i+1] << bit_overflow); - } - bit_set[NUM_FIELDS - 1 - field_shift] >>= bit_shift; - } - } - - /// Helper: call ROTATE with negative number instead - void RotateLeft(const size_t shift_size_raw) { - const field_t shift_size = shift_size_raw % NUM_BITS; - - // use different approaches based on BitSet size - if constexpr (NUM_FIELDS == 1) { - // special case: for exactly one field_T, try to go low level - // adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c - field_t & n = bit_set[0]; - field_t c = shift_size; - - // mask necessary to suprress shift count overflow warnings - constexpr field_t mask = MaskLow(FIELD_LOG2); - - c &= mask; - n = (n<>( (-(c+FIELD_BITS-NUM_BITS))&mask )); - - } else if (NUM_FIELDS < 32) { - // for small BitSets, shifting L/R and ORing is faster - emp::BitSet dup(*this); - dup.ShiftLeft(shift_size); - ShiftRight(NUM_BITS - shift_size); - OR_SELF(dup); - } else { - // for big BitSets, manual rotating is fater - - // note that we already modded shift_size by NUM_BITS - // so there's no need to mod by FIELD_SIZE here - const int field_shift = LAST_BIT ? ( - (shift_size + FIELD_BITS - LAST_BIT) / FIELD_BITS - ) : ( - shift_size / FIELD_BITS - ); - // if we field shift, we need to shift bits by (FIELD_BITS - LAST_BIT) - // more to account for the filler that gets pulled out of the middle - const int bit_shift = LAST_BIT && field_shift ? ( - (shift_size + FIELD_BITS - LAST_BIT) % FIELD_BITS - ) : ( - shift_size % FIELD_BITS - ); - const int bit_overflow = FIELD_BITS - bit_shift; - - // if rotating more than field capacity, we need to rotate fields - std::rotate( - std::rbegin(bit_set), - std::rbegin(bit_set)+field_shift, - std::rend(bit_set) - ); - - // if necessary, shift filler bits out of the middle - if constexpr ((bool)LAST_BIT) { - const int filler_idx = (NUM_FIELDS - 1 + field_shift) % NUM_FIELDS; - for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { - bit_set[i-1] |= bit_set[i] << LAST_BIT; - bit_set[i] >>= (FIELD_BITS - LAST_BIT); - } - } - - // account for bit_shift - if (bit_shift) { - - const field_t keystone = LAST_BIT ? ( - (bit_set[NUM_FIELDS - 1] << (FIELD_BITS - LAST_BIT)) - | (bit_set[NUM_FIELDS - 2] >> LAST_BIT) - ) : ( - bit_set[NUM_FIELDS - 1] - ); - - for (int i = NUM_FIELDS - 1; i > 0; --i) { - bit_set[i] <<= bit_shift; - bit_set[i] |= (bit_set[i-1] >> bit_overflow); - } - // Handle final field - bit_set[0] <<= bit_shift; - bit_set[0] |= keystone >> bit_overflow; - - } - - } - - // Mask out filler bits - if constexpr ((bool)LAST_BIT) { - bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - } - - } - - - /// Helper for calling ROTATE with positive number - void RotateRight(const size_t shift_size_raw) { - - const field_t shift_size = shift_size_raw % NUM_BITS; - - // use different approaches based on BitSet size - if constexpr (NUM_FIELDS == 1) { - // special case: for exactly one field_t, try to go low level - // adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c - - field_t & n = bit_set[0]; - field_t c = shift_size; - - // mask necessary to suprress shift count overflow warnings - constexpr field_t mask = MaskLow(FIELD_LOG2); - - c &= mask; - n = (n>>c) | (n<<( (NUM_BITS-c)&mask )); - - } else if (NUM_FIELDS < 32) { - // for small BitSets, shifting L/R and ORing is faster - emp::BitSet dup(*this); - dup.ShiftRight(shift_size); - ShiftLeft(NUM_BITS - shift_size); - OR_SELF(dup); - } else { - // for big BitSets, manual rotating is fater - - const field_t field_shift = (shift_size / FIELD_BITS) % NUM_FIELDS; - const int bit_shift = shift_size % FIELD_BITS; - const field_t bit_overflow = FIELD_BITS - bit_shift; - - // if rotating more than field capacity, we need to rotate fields - std::rotate( - std::begin(bit_set), - std::begin(bit_set)+field_shift, - std::end(bit_set) - ); - - // if necessary, shift filler bits out of the middle - if constexpr ((bool)LAST_BIT) { - const int filler_idx = NUM_FIELDS - 1 - field_shift; - for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { - bit_set[i-1] |= bit_set[i] << LAST_BIT; - bit_set[i] >>= (FIELD_BITS - LAST_BIT); - } - } - - // account for bit_shift - if (bit_shift) { - - const field_t keystone = LAST_BIT ? ( - bit_set[0] >> (FIELD_BITS - LAST_BIT) - ) : ( - bit_set[0] - ); - - if constexpr ((bool)LAST_BIT) { - bit_set[NUM_FIELDS-1] |= bit_set[0] << LAST_BIT; - } - - for (size_t i = 0; i < NUM_FIELDS - 1; ++i) { - bit_set[i] >>= bit_shift; - bit_set[i] |= (bit_set[i+1] << bit_overflow); - } - bit_set[NUM_FIELDS - 1] >>= bit_shift; - bit_set[NUM_FIELDS - 1] |= keystone << bit_overflow; - } - } - - // Mask out filler bits - if constexpr ((bool)LAST_BIT) { - bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - } - - } - - public: - /// Constructor: Assume all zeroes in set - BitSet() { Clear(); } - - /// Copy constructor from another BitSet - BitSet(const BitSet & in_set) { Copy(in_set.bit_set); } - - /// Constructor to generate a random BitSet (with equal prob of 0 or 1). - BitSet(Random & random) { Randomize(random); } - - /// Constructor to generate a random BitSet with provided prob of 1's. - BitSet(Random & random, const double p1) { Clear(); Randomize(random, p1); } - - /// Constructor to generate a BitSet from a std::bitset. - explicit BitSet(const std::bitset& bitset) { - Clear(); // have to clear out field bits beyond NUM_BITS - for (size_t bit{}; bit < NUM_BITS; ++bit) Set( bit, bitset[bit] ); - } - - /// Constructor to generate a BitSet from a string. - explicit BitSet(const std::string& bitstring) - : BitSet( std::bitset( bitstring ) ) - { emp_assert( bitstring.size() == NUM_BITS ); } - - /// Constructor to fill in a bit set from a vector. - template - BitSet(const std::initializer_list l) { - // TODO: should we enforce the initializer list to be the same length as the bitset? - // emp_assert(l.size() == NUM_BITS); - - // check that initializer list isn't longer than bitset - emp_assert(l.size() <= NUM_BITS); - - Clear(); - - size_t idx = 0; - for (auto i = std::rbegin(l); i != std::rend(l); ++i) { - Set(idx, *i); - ++idx; - } - - } - - /// Destructor. - ~BitSet() = default; - - /// Assignment operator. - BitSet & operator=(const BitSet & in_set) { - Copy(in_set.bit_set); - return *this; - } - - /// Set all bits randomly, with a 50% probability of being a 0 or 1. - void Randomize(Random & random) { - // Randomize all fields, then mask off bits in the last field if not complete. - - random.RandFill( - reinterpret_cast(bit_set), - (NUM_BITS+7)/8 - ); - - if constexpr (static_cast(LAST_BIT)) { - bit_set[NUM_FIELDS-1] &= MaskLow(NUM_BITS%32); - } - - } - - /// Set all bits randomly, with a given probability of being a 1. - void Randomize(Random & random, const double p1) { - if (p1 == 0.5) return Randomize(random); // If 0.5 probability, generate by field! - for (size_t i = 0; i < NUM_BITS; i++) Set(i, random.P(p1)); - } - - /// Mutate bits, return how many mutations were performed - size_t Mutate( - Random & random, - const size_t num_muts, // @CAO: use tools/Binomial in Distribution.h with this part? - const size_t min_idx=0 // draw this from a distribution to make some - // bits more volatile than others - ) { - emp_assert(min_idx <= NUM_BITS); - emp_assert(num_muts <= NUM_BITS - min_idx); - - std::vector res; - Choose(random, NUM_BITS - min_idx, num_muts, res); - - for (size_t idx : res) Toggle(idx + min_idx); - - return num_muts; - - } - - /// Assign from a BitSet of a different size. - template - BitSet & Import( - const BitSet & from_set, - const size_t from_bit=0 - ) { - - if constexpr (FROM_BITS == NUM_BITS) emp_assert(&from_set != this); - - emp_assert(from_bit < FROM_BITS); - - if (FROM_BITS - from_bit < NUM_BITS) Clear(); - - const size_t DEST_BYTES = (NUM_BITS + 7)/8; - const size_t FROM_BYTES = (FROM_BITS + 7)/8 - from_bit/8; - - const size_t COPY_BYTES = std::min(DEST_BYTES, FROM_BYTES); - - std::memcpy( - bit_set, - reinterpret_cast(from_set.bit_set) + from_bit/8, - COPY_BYTES - ); - - if (from_bit%8) { - - this->ShiftRight(from_bit%8); - - if (FROM_BYTES > COPY_BYTES) { - reinterpret_cast(bit_set)[COPY_BYTES-1] |= ( - reinterpret_cast( - from_set.bit_set - )[from_bit/8 + COPY_BYTES] - << (8 - from_bit%8) - ); - } - - } - - // mask out filler bits - if constexpr (static_cast(LAST_BIT)) { - bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - } - - return *this; - - } - - /// Convert to a Bitset of a different size. - template - BitSet Export(size_t start_bit=0) const { - - BitSet out_bits; - out_bits.Import(*this, start_bit); - - return out_bits; - } - - /// Test if two BitSet objects are identical. - bool operator==(const BitSet & in_set) const { - for (size_t i = 0; i < NUM_FIELDS; ++i) { - if (bit_set[i] != in_set.bit_set[i]) return false; - } - return true; - } - - /// Compare two BitSet objects, based on the associated binary value. - bool operator<(const BitSet & in_set) const { - for (int i = NUM_FIELDS-1; i >= 0; --i) { // Start loop at the largest field. - if (bit_set[i] == in_set.bit_set[i]) continue; // If same, keep looking! - return (bit_set[i] < in_set.bit_set[i]); // Otherwise, do comparison - } - return false; - } - - /// Compare two BitSet objects, based on the associated binary value. - bool operator<=(const BitSet & in_set) const { - for (int i = NUM_FIELDS-1; i >= 0; --i) { // Start loop at the largest field. - if (bit_set[i] == in_set.bit_set[i]) continue; // If same, keep looking! - return (bit_set[i] < in_set.bit_set[i]); // Otherwise, do comparison - } - return true; - } - - /// Test if two BitSet objects are different. - bool operator!=(const BitSet & in_set) const { return !operator==(in_set); } - - /// Compare two BitSet objects, based on the associated binary value. - bool operator>(const BitSet & in_set) const { return !operator<=(in_set); } - - /// Compare two BitSet objects, based on the associated binary value. - bool operator>=(const BitSet & in_set) const { return !operator<(in_set); } - - /// How many bits are in this BitSet? - constexpr static size_t GetSize() { return NUM_BITS; } - - /// How many bytes are in this BitSet? - constexpr static size_t GetNumBytes() { return NUM_BYTES; } - - /// Retrieve the bit as a specified index. - bool Get(size_t index) const { - emp_assert(index >= 0 && index < NUM_BITS); - const size_t field_id = FieldID(index); - const size_t pos_id = FieldPos(index); - return (bit_set[field_id] & (((field_t)1U) << pos_id)) != 0; - } - - /// Set the bit at a specified index. - void Set(size_t index, bool value=true) { - emp_assert(index < NUM_BITS); - const size_t field_id = FieldID(index); - const size_t pos_id = FieldPos(index); - const field_t pos_mask = ((field_t)1U) << pos_id; - - if (value) bit_set[field_id] |= pos_mask; - else bit_set[field_id] &= ~pos_mask; - } - - /// Flip all bits in this BitSet - BitSet & Toggle() { return NOT_SELF(); } - - /// Flip a single bit - BitSet & Toggle(size_t index) { - emp_assert(index >= 0 && index < NUM_BITS); - const size_t field_id = FieldID(index); - const size_t pos_id = FieldPos(index); - (bit_set[field_id] ^= (((field_t)1U) << pos_id)); - return *this; - } - - /// Flips all the bits in a range [start, end) - BitSet & Toggle(size_t start, size_t end) { - emp_assert(start <= end && end <= NUM_BITS); - for(size_t index = start; index < end; index++) { - Toggle(index); - } - return *this; - } - - /// Get the full byte starting from the bit at a specified index. - uint8_t GetByte(size_t index) const { - emp_assert(index < NUM_BYTES); - const size_t field_id = Byte2Field(index); - const size_t pos_id = Byte2FieldPos(index); - return (bit_set[field_id] >> pos_id) & 255; - } - - /// Get a read-only view into the internal array used by BitSet. - /// @return Read-only span of BitSet's bytes. - std::span GetBytes() const { - return std::span( - reinterpret_cast(bit_set), - NUM_BYTES - ); - } - - /// Set the full byte starting at the bit at the specified index. - void SetByte(size_t index, uint8_t value) { - emp_assert(index < NUM_BYTES); - const size_t field_id = Byte2Field(index); - const size_t pos_id = Byte2FieldPos(index); - const field_t val_uint = value; - bit_set[field_id] = (bit_set[field_id] & ~(((field_t)255U) << pos_id)) | (val_uint << pos_id); - } - - /// Get the unsigned int; index in in 32-bit jumps - /// (i.e., this is a field ID not bit id) - uint32_t GetUInt(const size_t index) const { return GetUInt32(index); } - - /// Set the unsigned int; index in in 32-bit jumps - /// (i.e., this is a field ID not bit id) - void SetUInt(const size_t index, const uint32_t value) { - SetUInt32(index, value); - } - - /// Get the field_t unsigned int; index in in 32-bit jumps - /// (i.e., this is a field ID not bit id) - uint32_t GetUInt32(const size_t index) const { - emp_assert(index * 32 < NUM_BITS); - - uint32_t res; - - std::memcpy( - &res, - reinterpret_cast(bit_set) + index * (32/8), - sizeof(res) - ); - - return res; - } - - /// Set the field_t unsigned int; index in in 32-bit jumps - /// (i.e., this is a field ID not bit id) - void SetUInt32(const size_t index, const uint32_t value) { - emp_assert(index * 32 < NUM_BITS); - - std::memcpy( - reinterpret_cast(bit_set) + index * (32/8), - &value, - sizeof(value) - ); - - // Mask out filler bits if necessary - if constexpr (static_cast(LAST_BIT)) { - // we only need to do this - // if (index * 32 == (NUM_FIELDS - 1) * FIELD_BITS) - // but just doing it always is probably faster - // check to make sure there are no leading ones in the unused bits - emp_assert((bit_set[NUM_FIELDS - 1] & ~MaskLow(LAST_BIT)) == 0); - } - - } - - /// Get the field_t unsigned int; index in in 64-bit jumps - /// (i.e., this is a field ID not bit id) - uint64_t GetUInt64(const size_t index) const { - emp_assert(index * 64 < NUM_BITS); - - uint64_t res = 0; - - if constexpr (FIELD_BITS == 64) { - res = bit_set[index]; - } else if constexpr (FIELD_BITS == 32 && (NUM_FIELDS % 2 == 0)) { - std::memcpy( - &res, - reinterpret_cast(bit_set) + index * (64/8), - sizeof(res) - ); - } else if constexpr (FIELD_BITS == 32 && NUM_FIELDS == 1) { - std::memcpy( - &res, - reinterpret_cast(bit_set), - 32/8 - ); - } else { - std::memcpy( - &res, - reinterpret_cast(bit_set) + index * (64/8), - std::min(64, NUM_FIELDS * FIELD_BITS - 64 * index)/8 - ); - } - - return res; - - } - - /// Set the field_t unsigned int; index in in 64-bit jumps - /// (i.e., this is a field ID not bit id) - void SetUInt64(const size_t index, const uint64_t value) { - emp_assert(index * 64 < NUM_BITS); - - if constexpr (FIELD_BITS == 64) { - bit_set[index] = value; - } else if constexpr (FIELD_BITS == 32 && (NUM_FIELDS % 2 == 0)) { - std::memcpy( - reinterpret_cast(bit_set) + index * (64/8), - &value, - sizeof(value) - ); - } else if constexpr (FIELD_BITS == 32 && NUM_FIELDS == 1) { - std::memcpy( - reinterpret_cast(bit_set), - &value, - 32/8 - ); - } else { - std::memcpy( - reinterpret_cast(bit_set) + index * (64/8), - &value, - std::min(64, NUM_FIELDS * FIELD_BITS - 64 * index)/8 - ); - } - - // Mask out filler bits if necessary - if constexpr (static_cast(LAST_BIT)) { - // we only need to do this - // if (index * 64 == (NUM_FIELDS - 1) * FIELD_BITS) - // but just doing it always is probably faster - // check to make sure there are no leading ones in the unused bits - emp_assert((bit_set[NUM_FIELDS - 1] & ~MaskLow(LAST_BIT)) == 0); - } - - - } - - /// Get the full uint32_t unsigned int starting from the bit at a specified index. - uint32_t GetUIntAtBit(const size_t index) { return GetUInt32AtBit(index); } - - /// Get the full uint32_t unsigned int starting from the bit at a specified index. - uint32_t GetUInt32AtBit(const size_t index) { - emp_assert(index < NUM_BITS); - - BitSet<32> res; - res.Import(*this, index); - - return res.GetUInt32(0); - - } - - /// Get OUT_BITS bits starting from the bit at a specified index (max 32) - template - uint32_t GetValueAtBit(const size_t index) { - static_assert(OUT_BITS <= 32, "Requesting too many bits to fit in a UInt"); - return GetUIntAtBit(index) & MaskLow(OUT_BITS); - } - - /// Get the unsigned numeric value represented by the BitSet as a double - double GetDouble() const { - - if constexpr (NUM_BITS <= 64) { - uint64_t res{}; - std::memcpy(&res, bit_set, NUM_BYTES); - return res; - } else { - double res = 0.0; - for (size_t i = 0; i < (NUM_BITS + 63) / 64; ++i) { - res += GetUInt64(i) * emp::Pow2(i * 64); - } - return res; - } - - } - - /// What is the maximum value this BitSet could contain, as a double? - static constexpr double MaxDouble() { return emp::Pow2(NUM_BITS) - 1.0; } - - /// Return true if ANY bits in the BitSet are one, else return false. - bool Any() const { for (auto i : bit_set) if (i) return true; return false; } - - /// Return true if NO bits in the BitSet are one, else return false. - bool None() const { return !Any(); } - - /// Return true if ALL bits in the BitSet are one, else return false. - bool All() const { return (~(*this)).None(); } - - /// Index into a const BitSet (i.e., cannot be set this way.) - bool operator[](size_t index) const { return Get(index); } - - /// Index into a BitSet, returning a proxy that will allow bit assignment to work. - BitProxy operator[](size_t index) { return BitProxy(*this, index); } - - /// Set all bits to zero. - void Clear() { std::memset(bit_set, 0, sizeof(bit_set)); } - - /// Set all bits to one. - void SetAll() { - std::memset(bit_set, 255, sizeof(bit_set));; - if constexpr (static_cast(LAST_BIT)) { - bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - } - } - - /// Overload ostream operator to return Print. - friend std::ostream& operator<<(std::ostream &out, const BitSet& bs){ - bs.Print(out); - return out; - } - - /// Print all bits to the provided output stream. - void Print(std::ostream & out=std::cout) const { - for (size_t i = NUM_BITS; i > 0; i--) { out << Get(i-1); } - } - - /// Print all bits from smallest to largest, as if this were an array, not a bit representation. - void PrintArray(std::ostream & out=std::cout) const { - for (size_t i = 0; i < NUM_BITS; i++) out << Get(i); - } - - /// Print the locations of all one bits, using the provided spacer (default is a single space) - void PrintOneIDs(std::ostream & out=std::cout, char spacer=' ') const { - for (size_t i = 0; i < NUM_BITS; i++) { if (Get(i)) out << i << spacer; } - } - - /// Count 1's by looping through once for each bit equal to 1 - size_t CountOnes_Sparse() const { - size_t bit_count = 0; - for (auto i : bit_set) { - while (i) { - i &= (i-1); // Peel off a single 1. - bit_count++; // And increment the counter - } - } - return bit_count; - } - - /// Count 1's in semi-parallel; fastest for even 0's & 1's - size_t CountOnes_Mixed() const { - - size_t bit_count = 0; - for (size_t f = 0; f < NUM_FIELDS; ++f) { - // when compiling with -O3 and -msse4.2, this is the fastest population count method. - // this is due to using a dedicated instuction that runs in 1 clock cycle. - std::bitset std_bs(bit_set[f]); - bit_count += std_bs.count(); - } - - return bit_count; - } - - /// Count the number of ones in the BitSet using bit tricks for a speedup. - size_t CountOnes() const { return CountOnes_Mixed(); } - - /// Return the index of the first one in the sequence; return -1 if no ones are available. - int FindBit() const { - size_t field_id = 0; - while (field_id < NUM_FIELDS && bit_set[field_id]==0) field_id++; - return (field_id < NUM_FIELDS) ? (int) (find_bit(bit_set[field_id]) + (field_id << FIELD_LOG2)) : -1; - } - - /// Return index of first one in sequence (or -1 if no ones); change this position to zero. - int PopBit() { - size_t field_id = 0; - while (field_id < NUM_FIELDS && bit_set[field_id]==0) field_id++; - if (field_id == NUM_FIELDS) return -1; // Failed to find bit! - - const int pos_found = (int) find_bit(bit_set[field_id]); - bit_set[field_id] &= ~(1U << pos_found); - return pos_found + (int)(field_id << FIELD_LOG2); - } - - /// Return index of first one in sequence AFTER start_pos (or -1 if no ones) - int FindBit(const size_t start_pos) const { - // @CAO -- There are better ways to do this with bit tricks - // (but start_pos is tricky...) - for (size_t i = start_pos; i < NUM_BITS; i++) { - if (Get(i)) return (int) i; - } - return -1; - } - - /// Return a vector indicating the posistions of all ones in the BitSet. - emp::vector GetOnes() const { - // @CAO -- There are better ways to do this with bit tricks. - emp::vector out_set(CountOnes()); - size_t cur_pos = 0; - for (size_t i = 0; i < NUM_BITS; i++) { - if (Get(i)) out_set[cur_pos++] = i; - } - return out_set; - } - - /// Finds the length of the longest segment of ones. - size_t LongestSegmentOnes() const { - size_t length = 0; - BitSet out_set(*this); - while(out_set.Any()){ - out_set.AND_SELF(out_set<<1); - ++length; - } - return length; - } - - - /// Perform a Boolean NOT on this BitSet and return the result. - BitSet NOT() const { - BitSet out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = ~bit_set[i]; - if (LAST_BIT > 0) out_set.bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - return out_set; - } - - /// Perform a Boolean AND with a second BitSet and return the result. - BitSet AND(const BitSet & set2) const { - BitSet out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = bit_set[i] & set2.bit_set[i]; - return out_set; - } - - /// Perform a Boolean OR with a second BitSet and return the result. - BitSet OR(const BitSet & set2) const { - BitSet out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = bit_set[i] | set2.bit_set[i]; - return out_set; - } - - /// Perform a Boolean NAND with a second BitSet and return the result. - BitSet NAND(const BitSet & set2) const { - BitSet out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = ~(bit_set[i] & set2.bit_set[i]); - if (LAST_BIT > 0) out_set.bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - return out_set; - } - - /// Perform a Boolean NOR with a second BitSet and return the result. - BitSet NOR(const BitSet & set2) const { - BitSet out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = ~(bit_set[i] | set2.bit_set[i]); - if (LAST_BIT > 0) out_set.bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - return out_set; - } - - /// Perform a Boolean XOR with a second BitSet and return the result. - BitSet XOR(const BitSet & set2) const { - BitSet out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = bit_set[i] ^ set2.bit_set[i]; - return out_set; - } - - /// Perform a Boolean EQU with a second BitSet and return the result. - BitSet EQU(const BitSet & set2) const { - BitSet out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = ~(bit_set[i] ^ set2.bit_set[i]); - if (LAST_BIT > 0) out_set.bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - return out_set; - } - - - /// Perform a Boolean NOT on this BitSet, store result here, and return this object. - BitSet & NOT_SELF() { - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = ~bit_set[i]; - if (LAST_BIT > 0) bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - return *this; - } - - /// Perform a Boolean AND with a second BitSet, store result here, and return this object. - BitSet & AND_SELF(const BitSet & set2) { - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = bit_set[i] & set2.bit_set[i]; - return *this; - } - - /// Perform a Boolean OR with a second BitSet, store result here, and return this object. - BitSet & OR_SELF(const BitSet & set2) { - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = bit_set[i] | set2.bit_set[i]; - return *this; - } - - /// Perform a Boolean NAND with a second BitSet, store result here, and return this object. - BitSet & NAND_SELF(const BitSet & set2) { - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = ~(bit_set[i] & set2.bit_set[i]); - if (LAST_BIT > 0) bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - return *this; - } - - /// Perform a Boolean NOR with a second BitSet, store result here, and return this object. - BitSet & NOR_SELF(const BitSet & set2) { - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = ~(bit_set[i] | set2.bit_set[i]); - if (LAST_BIT > 0) bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - return *this; - } - - /// Perform a Boolean XOR with a second BitSet, store result here, and return this object. - BitSet & XOR_SELF(const BitSet & set2) { - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = bit_set[i] ^ set2.bit_set[i]; - return *this; - } - - /// Perform a Boolean EQU with a second BitSet, store result here, and return this object. - BitSet & EQU_SELF(const BitSet & set2) { - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = ~(bit_set[i] ^ set2.bit_set[i]); - if (LAST_BIT > 0) bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - return *this; - } - - /// Positive shifts go right and negative shifts go left (0 does nothing); - /// return result. - BitSet SHIFT(const int shift_size) const { - BitSet out_set(*this); - if (shift_size > 0) out_set.ShiftRight((field_t) shift_size); - else if (shift_size < 0) out_set.ShiftLeft((field_t) (-shift_size)); - return out_set; - } - - /// Positive shifts go right and negative shifts go left (0 does nothing); - /// store result here, and return this object. - BitSet & SHIFT_SELF(const int shift_size) { - if (shift_size > 0) ShiftRight((field_t) shift_size); - else if (shift_size < 0) ShiftLeft((field_t) -shift_size); - return *this; - } - - /// Reverse the order of bits in the bitset - BitSet & REVERSE_SELF() { - - // reverse bytes - std::reverse( - reinterpret_cast(bit_set), - reinterpret_cast(bit_set) + NUM_BYTES - ); - - // reverse each byte - // adapted from https://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte - for (size_t i = 0; i < NUM_BYTES; ++i) { - unsigned char & b = reinterpret_cast(bit_set)[i]; - b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; - b = (b & 0xCC) >> 2 | (b & 0x33) << 2; - b = (b & 0xAA) >> 1 | (b & 0x55) << 1; - } - - // shift out filler bits - if constexpr (static_cast((8-NUM_BITS%8)%8)) { - this->ShiftRight((8-NUM_BITS%8)%8); - } - - return *this; - - } - - /// Reverse order of bits in the bitset. - BitSet REVERSE() const { - BitSet out_set(*this); - return out_set.REVERSE_SELF(); - } - - - /// Positive rotates go left and negative rotates go left (0 does nothing); - /// return result. - BitSet ROTATE(const int rotate_size) const { - BitSet out_set(*this); - if (rotate_size > 0) out_set.RotateRight((field_t) rotate_size); - else if (rotate_size < 0) out_set.RotateLeft((field_t) (-rotate_size)); - return out_set; - } - - /// Positive rotates go right and negative rotates go left (0 does nothing); - /// store result here, and return this object. - BitSet & ROTATE_SELF(const int rotate_size) { - if (rotate_size > 0) RotateRight((field_t) rotate_size); - else if (rotate_size < 0) RotateLeft((field_t) -rotate_size); - return *this; - } - - /// Helper: call ROTATE with negative number instead - template - BitSet & ROTL_SELF() { - constexpr field_t shift_size = shift_size_raw % NUM_BITS; - - // special case: for exactly one field_t, try to go low level - // adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c - if constexpr (NUM_FIELDS == 1) { - field_t & n = bit_set[0]; - field_t c = shift_size; - - // mask necessary to suprress shift count overflow warnings - constexpr field_t mask = MaskLow(FIELD_LOG2); - - c &= mask; - n = (n<>( (-(c+FIELD_BITS-NUM_BITS))&mask )); - - } else { - - // note that we already modded shift_size by NUM_BITS - // so there's no need to mod by FIELD_SIZE here - constexpr int field_shift = LAST_BIT ? ( - (shift_size + FIELD_BITS - LAST_BIT) / FIELD_BITS - ) : ( - shift_size / FIELD_BITS - ); - // if we field shift, we need to shift bits by (FIELD_BITS - LAST_BIT) - // more to account for the filler that gets pulled out of the middle - constexpr int bit_shift = LAST_BIT && field_shift ? ( - (shift_size + FIELD_BITS - LAST_BIT) % FIELD_BITS - ) : ( - shift_size % FIELD_BITS - ); - constexpr int bit_overflow = FIELD_BITS - bit_shift; - - // if rotating more than field capacity, we need to rotate fields - if constexpr ((bool)field_shift) { - std::rotate( - std::rbegin(bit_set), - std::rbegin(bit_set)+field_shift, - std::rend(bit_set) - ); - } - - // if necessary, shift filler bits out of the middle - if constexpr ((bool)LAST_BIT) { - const int filler_idx = (NUM_FIELDS - 1 + field_shift) % NUM_FIELDS; - for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { - bit_set[i-1] |= bit_set[i] << LAST_BIT; - bit_set[i] >>= (FIELD_BITS - LAST_BIT); - } - } - - // account for bit_shift - if (bit_shift) { - - const field_t keystone = LAST_BIT ? ( - (bit_set[NUM_FIELDS - 1] << (FIELD_BITS - LAST_BIT)) - | (bit_set[NUM_FIELDS - 2] >> LAST_BIT) - ) : ( - bit_set[NUM_FIELDS - 1] - ); - - for (int i = NUM_FIELDS - 1; i > 0; --i) { - bit_set[i] <<= bit_shift; - bit_set[i] |= (bit_set[i-1] >> bit_overflow); - } - // Handle final field - bit_set[0] <<= bit_shift; - bit_set[0] |= keystone >> bit_overflow; - - } - - } - - // mask out filler bits - if constexpr ((bool)LAST_BIT) { - bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - } - - return *this; - - } - - - /// Helper for calling ROTATE with positive number - template - BitSet & ROTR_SELF() { - - constexpr field_t shift_size = shift_size_raw % NUM_BITS; - - // special case: for exactly one field_t, try to go low level - // adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c - if constexpr (NUM_FIELDS == 1) { - field_t & n = bit_set[0]; - field_t c = shift_size; - - // mask necessary to suprress shift count overflow warnings - constexpr field_t mask = MaskLow(FIELD_LOG2); - - c &= mask; - n = (n>>c) | (n<<( (NUM_BITS-c)&mask )); - - } else { - - constexpr field_t field_shift = (shift_size / FIELD_BITS) % NUM_FIELDS; - constexpr int bit_shift = shift_size % FIELD_BITS; - constexpr field_t bit_overflow = FIELD_BITS - bit_shift; - - // if rotating more than field capacity, we need to rotate fields - if constexpr ((bool)field_shift) { - std::rotate( - std::begin(bit_set), - std::begin(bit_set)+field_shift, - std::end(bit_set) - ); - } - - // if necessary, shift filler bits out of the middle - if constexpr ((bool)LAST_BIT) { - constexpr int filler_idx = NUM_FIELDS - 1 - field_shift; - for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { - bit_set[i-1] |= bit_set[i] << LAST_BIT; - bit_set[i] >>= (FIELD_BITS - LAST_BIT); - } - } - - // account for bit_shift - if (bit_shift) { - - const field_t keystone = LAST_BIT ? ( - bit_set[0] >> (FIELD_BITS - LAST_BIT) - ) : ( - bit_set[0] - ); - - if constexpr ((bool)LAST_BIT) { - bit_set[NUM_FIELDS-1] |= bit_set[0] << LAST_BIT; - } - - for (size_t i = 0; i < NUM_FIELDS - 1; ++i) { - bit_set[i] >>= bit_shift; - bit_set[i] |= (bit_set[i+1] << bit_overflow); - } - bit_set[NUM_FIELDS - 1] >>= bit_shift; - bit_set[NUM_FIELDS - 1] |= keystone << bit_overflow; - } - } - - // mask out filler bits - if constexpr ((bool)LAST_BIT) { - bit_set[NUM_FIELDS - 1] &= MaskLow(LAST_BIT); - } - - return *this; - - } - - /// Addition of two Bitsets. - /// Wraps if it overflows. - /// Returns result. - BitSet ADD(const BitSet & set2) const{ - BitSet out_set(*this); - return out_set.ADD_SELF(set2); - } - - /// Addition of two Bitsets. - /// Wraps if it overflows. - /// Returns this object. - BitSet & ADD_SELF(const BitSet & set2) { - bool carry = false; - - for (size_t i = 0; i < NUM_BITS/FIELD_BITS; ++i) { - field_t addend = set2.bit_set[i] + static_cast(carry); - carry = set2.bit_set[i] > addend; - - field_t sum = bit_set[i] + addend; - carry |= bit_set[i] > sum; - - bit_set[i] = sum; - } - - if constexpr (static_cast(LAST_BIT)) { - bit_set[NUM_BITS/FIELD_BITS] = ( - bit_set[NUM_BITS/FIELD_BITS] - + set2.bit_set[NUM_BITS/FIELD_BITS] - + static_cast(carry) - ) & emp::MaskLow(LAST_BIT); - } - - return *this; - } - - /// Subtraction of two Bitsets. - /// Wraps around if it underflows. - /// Returns result. - BitSet SUB(const BitSet & set2) const{ - BitSet out_set(*this); - return out_set.SUB_SELF(set2); - } - - /// Subtraction of two Bitsets. - /// Wraps if it underflows. - /// Returns this object. - BitSet & SUB_SELF(const BitSet & set2){ - - bool carry = false; - - for (size_t i = 0; i < NUM_BITS/FIELD_BITS; ++i) { - field_t subtrahend = set2.bit_set[i] + static_cast(carry); - carry = set2.bit_set[i] > subtrahend; - carry |= bit_set[i] < subtrahend; - bit_set[i] -= subtrahend; - } - - if constexpr (static_cast(LAST_BIT)) { - bit_set[NUM_BITS/FIELD_BITS] = ( - bit_set[NUM_BITS/FIELD_BITS] - - set2.bit_set[NUM_BITS/FIELD_BITS] - - static_cast(carry) - ) & emp::MaskLow(LAST_BIT); - } - - return *this; - } - - /// Operator bitwise NOT... - BitSet operator~() const { return NOT(); } - - /// Operator bitwise AND... - BitSet operator&(const BitSet & ar2) const { return AND(ar2); } - - /// Operator bitwise OR... - BitSet operator|(const BitSet & ar2) const { return OR(ar2); } - - /// Operator bitwise XOR... - BitSet operator^(const BitSet & ar2) const { return XOR(ar2); } - - /// Operator shift left... - BitSet operator<<(const size_t shift_size) const { return SHIFT(-(int)shift_size); } - - /// Operator shift right... - BitSet operator>>(const size_t shift_size) const { return SHIFT((int)shift_size); } - - /// Compound operator bitwise AND... - const BitSet & operator&=(const BitSet & ar2) { return AND_SELF(ar2); } - - /// Compound operator bitwise OR... - const BitSet & operator|=(const BitSet & ar2) { return OR_SELF(ar2); } - - /// Compound operator bitwise XOR... - const BitSet & operator^=(const BitSet & ar2) { return XOR_SELF(ar2); } - - /// Compound operator shift left... - const BitSet & operator<<=(const size_t shift_size) { return SHIFT_SELF(-(int)shift_size); } - - /// Compound operator shift right... - const BitSet & operator>>=(const size_t shift_size) { return SHIFT_SELF((int)shift_size); } - - /// Operator plus... - BitSet operator+(const BitSet & ar2) const { return ADD(ar2); } - - /// Operator minus... - BitSet operator-(const BitSet & ar2) const { return SUB(ar2); } - - /// Compound operator plus... - const BitSet & operator+=(const BitSet & ar2) { return ADD_SELF(ar2); } - - /// Compoount operator minus... - const BitSet & operator-=(const BitSet & ar2) { return SUB_SELF(ar2); } - - /// Function to allow drop-in replacement with std::bitset. - constexpr static size_t size() { return NUM_BITS; } - - /// Function to allow drop-in replacement with std::bitset. - inline bool all() const { return All(); } - - /// Function to allow drop-in replacement with std::bitset. - inline bool any() const { return Any(); } - - /// Function to allow drop-in replacement with std::bitset. - inline bool none() const { return !Any(); } - - /// Function to allow drop-in replacement with std::bitset. - inline size_t count() const { return CountOnes_Mixed(); } - - /// Function to allow drop-in replacement with std::bitset. - inline BitSet & flip() { return Toggle(); } - - /// Function to allow drop-in replacement with std::bitset. - inline BitSet & flip(size_t pos) { return Toggle(pos); } - - /// Function to allow drop-in replacement with std::bitset. - inline BitSet & flip(size_t start, size_t end) { return Toggle(start, end); } - - template - void serialize( Archive & ar ) - { - ar( bit_set ); - } - - }; - - template - BitSet join(const BitSet & in1, const BitSet & in2) { - BitSet out_bits; - out_bits.Import(in2); - out_bits <<= NUM_BITS1; - out_bits |= in2.template Export(); - } - - /// Computes simple matching coefficient (https://en.wikipedia.org/wiki/Simple_matching_coefficient). template - double SimpleMatchCoeff(const BitSet & in1, const BitSet & in2) { - emp_assert(NUM_BITS > 0); // TODO: can be done with XOR - return (double)((in1 & in2).CountOnes() + (~in1 & ~in2).CountOnes()) / (double)NUM_BITS; - } - -} - -/// For hashing BitSets -namespace std { + using BitSet = emp::BitArray; - template - struct hash< emp::BitSet > { - size_t operator()( const emp::BitSet& bs ) const { - if constexpr ( 8 * sizeof( size_t ) == 32 ) { - if constexpr ( N <= 32 ) { - return bs.GetUInt32( 0 ); - } else if constexpr ( N <= 32 * 2 ) { - return emp::hash_combine( bs.GetUInt32( 0 ), bs.GetUInt32( 1 ) ); - } else if constexpr ( N <= 32 * 3 ) { - return emp::hash_combine( - emp::hash_combine( bs.GetUInt32( 0 ), bs.GetUInt32( 1 ) ), - bs.GetUInt32( 2 ) - ); - } else if constexpr ( N <= 32 * 4 ) { - return emp::hash_combine( - emp::hash_combine( bs.GetUInt32( 0 ), bs.GetUInt32( 1 ) ), - emp::hash_combine( bs.GetUInt32( 2 ), bs.GetUInt32( 3 ) ) - ); - } else return emp::murmur_hash( bs.GetBytes() ); - } else if constexpr ( 8 * sizeof( size_t ) == 64 ) { - if constexpr ( N <= 64 ) { - return bs.GetUInt64( 0 ); - } else if constexpr ( N <= 64 * 2 ) { - return emp::hash_combine( bs.GetUInt64( 0 ), bs.GetUInt64( 1 ) ); - } else return emp::murmur_hash( bs.GetBytes() ); - } else { emp_assert( false ); return 0; } - } - }; } - -#endif // #ifndef EMP_BIT_SET_HPP +#endif diff --git a/include/emp/bits/BitVector.hpp b/include/emp/bits/BitVector.hpp index 7b3e04737d..513d5211ec 100644 --- a/include/emp/bits/BitVector.hpp +++ b/include/emp/bits/BitVector.hpp @@ -1,24 +1,23 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2016-2020. + * @date 2016-2021. * * @file BitVector.hpp * @brief A drop-in replacement for std::vector, with additional bitwise logic features. * @note Status: RELEASE * - * Compile with -O3 and -msse4.2 for fast bit counting. + * @note Compile with -O3 and -msse4.2 for fast bit counting. * - * @todo Most of the operators don't check to make sure that both Bitvextors are the same size. + * @todo Most of the operators don't check to make sure that both BitVectors are the same size. * We should create versions (Intersection() and Union()?) that adjust sizes if needed. * * @todo Do small BitVector optimization. Currently we have number of bits (8 bytes) and a * pointer to the memory for the bitset (another 8 bytes), but we could use those 16 bytes * as 1 byte of size info followed by 15 bytes of bitset (120 bits!) - * @todo For BitVectors larger than 120 bits, we can use a factory to preserve bit info. - * @todo Implement append(), resize()... - * @todo Implement techniques to push bits (we have pop) - * @todo Implement techniques to insert or remove bits from middle. + * @todo For large BitVectors we can use a factory to preserve/adjust bit info. That should be + * just as efficient than a reserve, but without the need to store extra in-class info. + * @todo Implement append(), resize(), push_bit(), insert(), remove() * @todo Think about how itertors should work for BitVector. It should probably go bit-by-bit, * but there are very few circumstances where that would be useful. Going through the * positions of all ones would be more useful, but perhaps less intuitive. @@ -31,999 +30,2252 @@ #define EMP_BIT_VECTOR_H #include +#include +#include #include #include "../base/assert.hpp" #include "../base/Ptr.hpp" #include "../base/vector.hpp" +#include "../datastructs/hash_utils.hpp" #include "../math/math.hpp" +#include "../math/Random.hpp" +#include "../polyfill/span.hpp" + #include "bitset_utils.hpp" +#include "_bitset_helpers.hpp" namespace emp { /// @brief A drop-in replacement for std::vector, but with extra bitwise logic features. /// - /// This class stores an arbirary number of bits in a set of "fields" (either 32-bit or 64-bit, - /// depending on which should be faster.) Individual bits can be extracted, -or- bitwise logic - /// (or bit magic) can be used on the groups of bits, + /// This class stores an arbirary number of bits in a set of "fields" (typically 32 bits or 64 + /// bits per field, depending on which should be faster.) Individual bits can be extracted, + /// -or- bitwise logic (including more complex bit magic) can be used on the groups of bits. class BitVector { private: - // For the moment, field_t will always be equal to size_t. Since size_t is normally the native - // size for a processor (and, correctly, 32 bits for Emscripten), this should work in almost all - // cases. + // Use size_t for field_t since size_t is normally the native size for a processor (and, + // correctly, 32 bits for Emscripten), this should work in almost all cases. using field_t = size_t; - static constexpr size_t FIELD_BITS = sizeof(field_t)*8; ///< How many bits are in a field? - size_t num_bits; ///< How many total bits are we using? - Ptr bit_set; ///< What is the status of each bit? + // Compile-time constants + static constexpr size_t FIELD_BITS = sizeof(field_t)*8; ///< Number of bits in a field - /// End position of the stored bits in the last field; 0 if perfect fit. - size_t LastBitID() const { return num_bits & (FIELD_BITS - 1); } + static constexpr field_t FIELD_0 = (field_t) 0; ///< All bits in a field set to 0 + static constexpr field_t FIELD_1 = (field_t) 1; ///< Least significant bit set to 1 + static constexpr field_t FIELD_255 = (field_t) 255; ///< Least significant 8 bits set to 1 + static constexpr field_t FIELD_ALL = ~FIELD_0; ///< All bits in a field set to 1 + static constexpr size_t MAX_BITS = (size_t) -1; ///< Value larger than any bit ID. - /// How many feilds do we need? - size_t NumFields() const { return num_bits ? (1 + ((num_bits - 1) / FIELD_BITS)) : 0; } + // Number of bits needed to specify position in a field + mask + static constexpr size_t FIELD_LOG2 = emp::Log2(FIELD_BITS); + static constexpr field_t FIELD_LOG2_MASK = MaskLow(FIELD_LOG2); - /// How many bytes are used in the current vector (round up to whole bytes.) - size_t NumBytes() const { return num_bits ? (1 + ((num_bits - 1) >> 3)) : 0; } + size_t num_bits; ///< Total number of bits are we using + Ptr bits; ///< Pointer to array with the status of each bit - /// BitProxy lets us use operator[] on with BitVector as an lvalue. - struct BitProxy { - BitVector & bit_vector; ///< Which BitVector does this proxy belong to? - size_t index; ///< Which position in the bit vector does this proxy point at? + /// Num bits used in partial field at the end; 0 if perfect fit. + [[nodiscard]] size_t NumEndBits() const { return num_bits & (FIELD_BITS - 1); } - /// Setup a new proxy with the associated vector and index. - BitProxy(BitVector & _v, size_t _idx) : bit_vector(_v), index(_idx) {;} + /// How many EXTRA bits are leftover in the gap at the end? + [[nodiscard]] size_t EndGap() const { return NumEndBits() ? (FIELD_BITS - NumEndBits()) : 0; } - /// Assignment operator to the bit associated with this proxy (as an lvalue). - BitProxy & operator=(bool b) { - bit_vector.Set(index, b); - return *this; - } + /// A mask to cut off all of the final bits. + [[nodiscard]] field_t EndMask() const { return MaskLow(NumEndBits()); } - /// Conversion of this proxy to Boolean (as an rvalue) - operator bool() const { - return bit_vector.Get(index); - } + /// How many feilds do we need for the current set of bits? + [[nodiscard]] size_t NumFields() const { return num_bits ? (1 + ((num_bits - 1) / FIELD_BITS)) : 0; } - /// Compound assignement operator AND using BitProxy as lvalue. - /// @note Implemented in BitProxy since it needs to work, but may not be efficient. - BitProxy & operator &=(bool b) { - const bool v = bit_vector.Get(index); - bit_vector.Set(index, v & b); - return *this; - } + /// What is the ID of the last occupied field? + [[nodiscard]] size_t LastField() const { return NumFields() - 1; } - /// Compound assignement operator OR using BitProxy as lvalue. - /// @note Implemented in BitProxy since it needs to work, but may not be efficient. - BitProxy & operator |=(bool b) { - const bool v = bit_vector.Get(index); - bit_vector.Set(index, v | b); - return *this; - } + /// How many bytes are used for the current set of bits? (rounded up!) + [[nodiscard]] size_t NumBytes() const { return num_bits ? (1 + ((num_bits - 1) >> 3)) : 0; } - /// Compound assignement operator XOR using BitProxy as lvalue. - /// @note Implemented in BitProxy since it needs to work, but may not be efficient. - BitProxy & operator ^=(bool b) { - const bool v = bit_vector.Get(index); - bit_vector.Set(index, v ^ b); - return *this; - } + /// How many bytes are allocated? (rounded up!) + [[nodiscard]] size_t TotalBytes() const { return NumFields() * sizeof(field_t); } - /// Compound assignement operator PLUS using BitProxy as lvalue. - /// @note Implemented in BitProxy since it needs to work, but may not be efficient. - BitProxy & operator +=(bool b) { - const bool v = bit_vector.Get(index); - bit_vector.Set(index, v || b); - return *this; - } + // Identify the field that a specified bit is in. + [[nodiscard]] static constexpr size_t FieldID(const size_t index) { return index / FIELD_BITS; } - /// Compound assignement operator MINUS using BitProxy as lvalue. - /// @note Implemented in BitProxy since it needs to work, but may not be efficient. - BitProxy & operator -=(bool b) { - const bool v = bit_vector.Get(index); - bit_vector.Set(index, v - b); - return *this; - } + // Identify the position within a field where a specified bit is. + [[nodiscard]] static constexpr size_t FieldPos(const size_t index) { return index & (FIELD_BITS-1); } - /// Compound assignement operator TIMES using BitProxy as lvalue. - /// @note Implemented in BitProxy since it needs to work, but may not be efficient. - BitProxy & operator *=(bool b) { - const bool v = bit_vector.Get(index); - bit_vector.Set(index, v && b); - return *this; - } + // Identify which field a specified byte position would be in. + [[nodiscard]] static constexpr size_t Byte2Field(const size_t index) { return index / sizeof(field_t); } - /// Compound assignement operator DIV using BitProxy as lvalue. - /// @note Implemented in BitProxy since it needs to work, but may not be efficient. - /// @note Never use this function except for consistency in a template since must divide by 1. - BitProxy & operator /=(bool b) { - emp_assert(b == true); - return *this; - } - }; + // Convert a byte position in BitVector to a byte position in the target field. + [[nodiscard]] static constexpr size_t Byte2FieldPos(const size_t index) { return FieldPos(index * 8); } - /// Identify the field that a specified bit is in. - static constexpr size_t FieldID(const size_t index) { return index / FIELD_BITS; } + // Assume that the size of the bits has already been adjusted to be the size of the one + // being copied and only the fields need to be copied over. + void RawCopy(const Ptr in); - /// Identify the position in a field where a specified bit is. - static constexpr size_t FieldPos(const size_t index) { return index & (FIELD_BITS-1); } + // Copy bits from one position in the genome to another; leave old positions unchanged. + void RawCopy(const size_t from_start, const size_t from_stop, const size_t to); - /// Identify which field a specified byte position would be in. - static constexpr size_t Byte2Field(const size_t index) { return index/sizeof(field_t); } + // Convert the bits to bytes (note that bits are NOT in order at the byte level!) + [[nodiscard]] emp::Ptr BytePtr() { return bits.ReinterpretCast(); } - /// Convert a byte position in BitVector to a byte position in the target field. - static constexpr size_t Byte2FieldPos(const size_t index) { - return (index & (sizeof(field_t)-1)) << 3; + // Convert the bits to const bytes array (note that bits are NOT in order at the byte level!) + [[nodiscard]] emp::Ptr BytePtr() const { + return bits.ReinterpretCast(); } - /// Assume that the size of the bit_set has already been adjusted to be the size of the one - /// being copied and only the fields need to be copied over. - void RawCopy(const Ptr in_set) { - #ifdef EMP_TRACK_MEM - emp_assert(in_set.IsNull() == false); - emp_assert(bit_set.DebugIsArray() && in_set.DebugIsArray()); - emp_assert(bit_set.DebugGetArrayBytes() == in_set.DebugGetArrayBytes(), - bit_set.DebugGetArrayBytes(), in_set.DebugGetArrayBytes()); - #endif + // Any bits past the last "real" bit in the last field should be kept as zeros. + void ClearExcessBits() { if (NumEndBits()) bits[LastField()] &= EndMask(); } - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = in_set[i]; - } + // Apply a transformation to each bit field in a specified range. + template + inline BitVector & ApplyRange(const FUN_T & fun, size_t start, size_t stop); - /// Helper: call SHIFT with positive number - void ShiftLeft(const size_t shift_size) { - const size_t field_shift = shift_size / FIELD_BITS; - const size_t bit_shift = shift_size % FIELD_BITS; - const size_t bit_overflow = FIELD_BITS - bit_shift; - const size_t NUM_FIELDS = NumFields(); + // Helper: call SHIFT with positive number + void ShiftLeft(const size_t shift_size); - // Loop through each field, from L to R, and update it. - if (field_shift) { - for (size_t i = NUM_FIELDS; i > field_shift; --i) { - bit_set[i-1] = bit_set[i - field_shift - 1]; - } - for (size_t i = field_shift; i > 0; --i) bit_set[i-1] = 0; - } + // Helper for calling SHIFT with negative number + void ShiftRight(const size_t shift_size); - // account for bit_shift - if (bit_shift) { - for (size_t i = NUM_FIELDS - 1; i > field_shift; --i) { - bit_set[i] <<= bit_shift; - bit_set[i] |= (bit_set[i-1] >> bit_overflow); - } - // Handle final field (field_shift position) - bit_set[field_shift] <<= bit_shift; - } + /// Helper: call ROTATE with negative number instead + void RotateLeft(const size_t shift_size_raw); - // Mask out any bits that have left-shifted away - const size_t last_bit_id = LastBitID(); - constexpr field_t val_one = 1; - if (last_bit_id) { bit_set[NUM_FIELDS - 1] &= (val_one << last_bit_id) - val_one; } - } + /// Helper for calling ROTATE with positive number + void RotateRight(const size_t shift_size_raw); + public: + /// Build a new BitVector with specified bit count (default 0) and initialization (default 0) + BitVector(size_t in_num_bits=0, bool init_val=false); - /// Helper for calling SHIFT with negative number - void ShiftRight(const size_t shift_size) { - const size_t field_shift = shift_size / FIELD_BITS; - const size_t bit_shift = shift_size % FIELD_BITS; - const size_t bit_overflow = FIELD_BITS - bit_shift; - const size_t NUM_FIELDS = NumFields(); - const size_t field_shift2 = NUM_FIELDS - field_shift; + // Prevent ambiguous conversions... + /// Anything not otherwise defined for first argument, convert to size_t. + template ::value, int>::type = 0> + BitVector(T in_num_bits) : BitVector((size_t) in_num_bits, 0) {} - // account for field_shift - if (field_shift) { - for (size_t i = 0; i < field_shift2; ++i) { - bit_set[i] = bit_set[i + field_shift]; - } - for (size_t i = field_shift2; i < NUM_FIELDS; i++) bit_set[i] = 0U; - } + /// Copy constructor of existing bit field. + BitVector(const BitVector & in); - // account for bit_shift - if (bit_shift) { - for (size_t i = 0; i < (field_shift2 - 1); ++i) { - bit_set[i] >>= bit_shift; - bit_set[i] |= (bit_set[i+1] << bit_overflow); - } - bit_set[field_shift2 - 1] >>= bit_shift; - } - } + /// Move constructor of existing bit field. + BitVector(BitVector && in); - public: - /// Build a new BitVector with specified bit count (default 0) and initialization (default 0) - BitVector(size_t in_num_bits=0, bool init_val=false) : num_bits(in_num_bits), bit_set(nullptr) { - if (num_bits) bit_set = NewArrayPtr(NumFields()); - if (init_val) SetAll(); else Clear(); - } + /// Constructor to generate a BitVector from a std::bitset. + template + explicit BitVector(const std::bitset & bitset); - /// Copy constructor of existing bit field. - BitVector(const BitVector & in_set) : num_bits(in_set.num_bits), bit_set(nullptr) { - #ifdef EMP_TRACK_MEM - emp_assert(in_set.bit_set.IsNull() || in_set.bit_set.DebugIsArray()); - emp_assert(in_set.bit_set.OK()); - #endif + /// Constructor to generate a BitVector from a string of '0's and '1's. + BitVector(const std::string & bitstring); - // There is only something to copy if there are a non-zero number of bits! - if (num_bits) { - #ifdef EMP_TRACK_MEM - emp_assert(!in_set.bit_set.IsNull() && in_set.bit_set.DebugIsArray(), in_set.bit_set.IsNull(), in_set.bit_set.DebugIsArray()); - #endif - bit_set = NewArrayPtr(NumFields()); - RawCopy(in_set.bit_set); - } - } + /// Constructor to generate a BitVector from a literal string of '0's and '1's. + BitVector(const char * bitstring) : BitVector(std::string(bitstring)) {} - /// Move constructor of existing bit field. - BitVector(BitVector && in_set) : num_bits(in_set.num_bits), bit_set(in_set.bit_set) { - #ifdef EMP_TRACK_MEM - emp_assert(bit_set == nullptr || bit_set.DebugIsArray()); - emp_assert(bit_set.OK()); - #endif + /// Constructor to generate a random BitVector (with equal prob of 0 or 1). + BitVector(size_t in_num_bits, Random & random); - in_set.bit_set = nullptr; - in_set.num_bits = 0; - } + /// Constructor to generate a random BitVector with provided prob of 1's. + BitVector(size_t in_num_bits, Random & random, const double p1); + + /// Constructor to generate a random BitVector with provided number of 1's. + BitVector(size_t in_num_bits, Random & random, const size_t target_ones); + + /// Constructor to generate a random BitVector with provided number of 1's. + BitVector(size_t in_num_bits, Random & random, const int target_ones) + : BitVector(in_num_bits, random, (size_t) target_ones) { } + + /// Initializer list constructor. + template BitVector(const std::initializer_list l); /// Copy, but with a resize. - BitVector(const BitVector & in_set, size_t new_size) : BitVector(in_set) { - if (num_bits != new_size) Resize(new_size); - } + BitVector(const BitVector & in, size_t new_size); /// Destructor - ~BitVector() { - if (bit_set) { // A move constructor can make bit_set == nullptr - bit_set.DeleteArray(); - bit_set = nullptr; - } - } + ~BitVector(); /// Assignment operator. - BitVector & operator=(const BitVector & in_set) { - #ifdef EMP_TRACK_MEM - emp_assert(in_set.bit_set == nullptr || in_set.bit_set.DebugIsArray()); - emp_assert(in_set.bit_set != nullptr || in_set.num_bits == 0); - emp_assert(in_set.bit_set.OK()); - #endif - - if (&in_set == this) return *this; - const size_t in_num_fields = in_set.NumFields(); - const size_t prev_num_fields = NumFields(); - num_bits = in_set.num_bits; + BitVector & operator=(const BitVector & in); - if (in_num_fields != prev_num_fields) { - if (bit_set) bit_set.DeleteArray(); - if (num_bits) bit_set = NewArrayPtr(in_num_fields); - else bit_set = nullptr; - } + /// Move operator. + BitVector & operator=(BitVector && in); - if (num_bits) RawCopy(in_set.bit_set); - return *this; - } + /// Assignment operator from a std::bitset. + template + BitVector & operator=(const std::bitset & bitset); - /// Move operator. - BitVector & operator=(BitVector && in_set) { - emp_assert(&in_set != this); // in_set is an r-value, so this shouldn't be possible... - if (bit_set) bit_set.DeleteArray(); // If we already had a bitset, get rid of it. - num_bits = in_set.num_bits; // Update the number of bits... - bit_set = in_set.bit_set; // And steal the old memory for what those bits are. - in_set.bit_set = nullptr; // Prepare in_set for deletion without deallocating. - in_set.num_bits = 0; - - return *this; - } + /// Assignment operator from a string of '0's and '1's. + BitVector & operator=(const std::string & bitstring); - template - operator emp::vector() { - emp::vector out(GetSize()); - for (size_t i = 0; i < GetSize(); i++) { - out[i] = (T) Get(i); - } - return out; - } + /// Assignment operator from a literal string of '0's and '1's. + BitVector & operator=(const char * bitstring) { return operator=(std::string(bitstring)); } - /// Resize this BitVector to have the specified number of bits. - BitVector & Resize(const size_t new_bits) { - const size_t old_num_fields = NumFields(); - num_bits = new_bits; - const size_t NUM_FIELDS = NumFields(); + /// Assignment from another BitVector without changing size. + BitVector & Import( const BitVector & from_bv, const size_t from_bit=0 ); - if (NUM_FIELDS == old_num_fields) { // We can use our existing bit field - num_bits = new_bits; - // If there are extra bits, zero them out. - if (LastBitID() > 0) bit_set[NUM_FIELDS - 1] &= emp::MaskLow(LastBitID()); - } + /// Convert to a BitVector of a different size. + BitVector Export(size_t out_size, size_t start_bit=0) const; - else { // We have to change the number of bitfields. Resize & copy old info. - Ptr old_bit_set = bit_set; - if (num_bits > 0) bit_set = NewArrayPtr(NUM_FIELDS); - else bit_set = nullptr; - const size_t min_fields = std::min(old_num_fields, NUM_FIELDS); - for (size_t i = 0; i < min_fields; i++) bit_set[i] = old_bit_set[i]; - for (size_t i = min_fields; i < NUM_FIELDS; i++) bit_set[i] = 0U; - if (old_bit_set) old_bit_set.DeleteArray(); - } + // Scan this bitvector to make sure that there are no internal problems. + bool OK() const; - return *this; - } - /// Test if two bit vectors are identical. - bool operator==(const BitVector & in_set) const { - if (num_bits != in_set.num_bits) return false; + // >>>>>>>>>> Accessors <<<<<<<<<< // - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; ++i) { - if (bit_set[i] != in_set.bit_set[i]) return false; - } - return true; - } + /// How many bits do we currently have? + [[nodiscard]] size_t GetSize() const { return num_bits; } - /// Compare the would-be numerical values of two bit vectors. - bool operator<(const BitVector & in_set) const { - if (num_bits != in_set.num_bits) return num_bits < in_set.num_bits; + /// How many bytes are in this BitVector? (includes empty field space) + [[nodiscard]] size_t GetNumBytes() const { return NumBytes(); } - const size_t NUM_FIELDS = NumFields(); - for (size_t i = NUM_FIELDS; i > 0; --i) { // Start loop at the largest field. - const size_t pos = i-1; - if (bit_set[pos] == in_set.bit_set[pos]) continue; // If same, keep looking! - return (bit_set[pos] < in_set.bit_set[pos]); // Otherwise, do comparison - } - return false; - } + /// How many distinct values could be held in this BitVector? + [[nodiscard]] double GetNumStates() const { return emp::Pow2(num_bits); } - /// Compare the would-be numerical values of two bit vectors. - bool operator<=(const BitVector & in_set) const { - if (num_bits != in_set.num_bits) return num_bits <= in_set.num_bits; + /// Retrive the bit value from the specified index. + [[nodiscard]] bool Get(size_t index) const; - const size_t NUM_FIELDS = NumFields(); - for (size_t i = NUM_FIELDS; i > 0; --i) { // Start loop at the largest field. - const size_t pos = i-1; - if (bit_set[pos] == in_set.bit_set[pos]) continue; // If same, keep looking! - return (bit_set[pos] < in_set.bit_set[pos]); // Otherwise, do comparison - } - return true; - } + /// A safe version of Get() for indexing out of range. Useful for representing collections. + [[nodiscard]] bool Has(size_t index) const { return (index < num_bits) ? Get(index) : false; } - /// Determine if two bit vectors are different. - bool operator!=(const BitVector & in_set) const { return !operator==(in_set); } + /// Update the bit value at the specified index. + BitVector & Set(size_t index, bool value=true); - /// Compare the would-be numerical values of two bit vectors. - bool operator>(const BitVector & in_set) const { return !operator<=(in_set); } + /// Set all bits to 1. + BitVector & SetAll(); - /// Compare the would-be numerical values of two bit vectors. - bool operator>=(const BitVector & in_set) const { return !operator<(in_set); } + /// Set a range of bits to one: [start, stop) + BitVector & SetRange(size_t start, size_t stop) + { return ApplyRange([](field_t){ return FIELD_ALL; }, start, stop); } - /// How many bits do we currently have? - size_t GetSize() const { return num_bits; } + /// Set all bits to 0. + BitVector & Clear(); - /// Retrive the bit value from the specified index. - bool Get(size_t index) const { - emp_assert(index < num_bits, index, num_bits); - const size_t field_id = FieldID(index); - const size_t pos_id = FieldPos(index); - return (bit_set[field_id] & (static_cast(1) << pos_id)) != 0; - } + /// Set specific bit to 0. + BitVector & Clear(size_t index) { return Set(index, false); } - /// A safe version of Get() for indexing out of range. Typically used when a BitVector - /// represents a collection. - bool Has(size_t index) const { - return (index < num_bits) ? Get(index) : false; - } + /// Set bits to 0 in the range [start, stop) + BitVector & Clear(const size_t start, const size_t stop) + { return ApplyRange([](field_t){ return 0; }, start, stop); } - /// Update the bit value at the specified index. - BitVector & Set(size_t index, bool value=true) { - emp_assert(index < num_bits, index, num_bits); - const size_t field_id = FieldID(index); - const size_t pos_id = FieldPos(index); - constexpr field_t val_one = 1; - const field_t pos_mask = val_one << pos_id; - if (value) bit_set[field_id] |= pos_mask; - else bit_set[field_id] &= ~pos_mask; + /// Const index operator -- return the bit at the specified position. + [[nodiscard]] bool operator[](size_t index) const { return Get(index); } - return *this; - } + /// Index operator -- return a proxy to the bit at the specified position so it can be an lvalue. + BitProxy operator[](size_t index) { return BitProxy(*this, index); } /// Change every bit in the sequence. BitVector & Toggle() { return NOT_SELF(); } /// Change a specified bit to the opposite value - BitVector & Toggle(size_t index) { - emp_assert(index < num_bits, index, num_bits); - const size_t field_id = FieldID(index); - const size_t pos_id = FieldPos(index); - constexpr field_t val_one = 1; - const field_t pos_mask = val_one << pos_id; + BitVector & Toggle(size_t index); - bit_set[field_id] ^= pos_mask; + /// Flips all the bits in a range [start, end) + BitVector & Toggle(size_t start, size_t stop) + { return ApplyRange([](field_t x){ return ~x; }, start, stop); } - return *this; - } + /// Return true if ANY bits are set to 1, otherwise return false. + [[nodiscard]] bool Any() const; - /// Flips all the bits in a range [start, end) - BitVector & Toggle(size_t start, size_t end) { - emp_assert(start <= end && end <= num_bits); - for(size_t index = start; index < end; index++) { - Toggle(index); - } - return *this; - } + /// Return true if NO bits are set to 1, otherwise return false. + [[nodiscard]] bool None() const { return !Any(); } - /// A simple hash function for bit vectors. - std::size_t Hash() const { - std::size_t hash_val = 0; - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; i++) { - hash_val ^= bit_set[i]; - } - return hash_val ^ ((97*num_bits) << 8); - } + /// Return true if ALL bits are set to 1, otherwise return false. + // @CAO: Can speed up by not duplicating the whole BitVector. + [[nodiscard]] bool All() const { return (~(*this)).None(); } - /// Retrive the byte at the specified byte index. - uint8_t GetByte(size_t index) const { - emp_assert(index < NumBytes(), index, NumBytes()); - const size_t field_id = Byte2Field(index); - const size_t pos_id = Byte2FieldPos(index); - return (bit_set[field_id] >> pos_id) & 255U; - } + /// Resize this BitVector to have the specified number of bits. + BitVector & Resize(size_t new_bits); - /// Update the byte at the specified byte index. - void SetByte(size_t index, uint8_t value) { - emp_assert(index < NumBytes(), index, NumBytes()); - const size_t field_id = Byte2Field(index); - const size_t pos_id = Byte2FieldPos(index); - const field_t val_uint = value; - bit_set[field_id] = (bit_set[field_id] & ~(static_cast(255) << pos_id)) | (val_uint << pos_id); - } - /// Retrieve the 32-bit uint from the specified uint index. - /* - uint32_t GetUInt(size_t index) const { - // If the fields are already 32 bits, return. - if constexpr (sizeof(field_t) == 4) return bit_set[index]; + // >>>>>>>>>> Randomization functions <<<<<<<<<< // - emp_assert(sizeof(field_t) == 8); + /// Set all bits randomly, with a 50% probability of being a 0 or 1. + BitVector & Randomize(Random & random); - const size_t field_id = index/2; - const size_t field_pos = 1 - (index & 1); + /// Set all bits randomly, with probability specified at compile time. + template + BitVector & RandomizeP(Random & random, const size_t start_pos=0, size_t stop_pos=MAX_BITS); - emp_assert(field_id < NumFields()); + /// Set all bits randomly, with a given probability of being a one. + BitVector & Randomize(Random & random, const double p, + const size_t start_pos=0, size_t stop_pos=MAX_BITS); - return (uint32_t) (bit_set[field_id] >> (field_pos * 32)); - } - */ - // Retrieve the 32-bit uint from the specified uint index. - // new implementation based on bitset.h GetUInt32 - uint32_t GetUInt(size_t index) const { - emp_assert(index * 32 < num_bits); - - uint32_t res; - - std::memcpy( - &res, - bit_set.Cast().Raw() + index * (32/8), - sizeof(res) - ); + /// Set all bits randomly, with a given number of ones. + BitVector & ChooseRandom(Random & random, const int target_ones, + const size_t start_pos=0, size_t stop_pos=MAX_BITS); + + /// Flip random bits with a given probability. + BitVector & FlipRandom(Random & random, const double p, + const size_t start_pos=0, size_t stop_pos=MAX_BITS); - return res; - } + /// Set random bits with a given probability (does not check if already set.) + BitVector & SetRandom(Random & random, const double p, + const size_t start_pos=0, size_t stop_pos=MAX_BITS); - /// Update the 32-bit uint at the specified uint index. - void SetUInt(const size_t index, uint32_t value) { - emp_assert(index * 32 < num_bits); + /// Unset random bits with a given probability (does not check if already zero.) + BitVector & ClearRandom(Random & random, const double p, + const size_t start_pos=0, size_t stop_pos=MAX_BITS); - std::memcpy( - bit_set.Cast().Raw() + index * (32/8), - &value, - sizeof(value) - ); + /// Flip a specified number of random bits. + BitVector & FlipRandomCount(Random & random, const size_t target_bits); - // check to make sure there are no leading ones in the unused bits - // or if LastBitID is 0 everything should pass too - emp_assert( - LastBitID() == 0 - || ( - bit_set[NumFields() - 1] - & ~emp::MaskLow(LastBitID()) - ) == 0, - LastBitID(), bit_set[NumFields() - 1] - ); + /// Set a specified number of random bits (does not check if already set.) + BitVector & SetRandomCount(Random & random, const size_t target_bits); - } + /// Unset a specified number of random bits (does not check if already zero.) + BitVector & ClearRandomCount(Random & random, const size_t target_bits); - void SetUIntAtBit(size_t index, uint32_t value) { - if constexpr (sizeof(field_t) == 4) bit_set[index] = value; - emp_assert(sizeof(field_t) == 8); + // >>>>>>>>>> Comparison Operators <<<<<<<<<< // - const size_t field_id = FieldID(index); - const size_t field_pos = FieldPos(index); - const field_t mask = ((field_t) ((uint32_t) -1)) << (1-field_pos); + [[nodiscard]] bool operator==(const BitVector & in) const; + [[nodiscard]] bool operator!=(const BitVector & in) const { return !(*this == in); } + [[nodiscard]] bool operator< (const BitVector & in) const; + [[nodiscard]] bool operator> (const BitVector & in) const { return in < *this; } + [[nodiscard]] bool operator<=(const BitVector & in) const { return !(in < *this); } + [[nodiscard]] bool operator>=(const BitVector & in) const { return !(*this < in); } - emp_assert(field_id < NumFields()); - bit_set[field_id] &= mask; // Clear out bits that we are setting. - bit_set[field_id] |= ((field_t) value) << (field_pos * 32); - } + // >>>>>>>>>> Conversion Operators <<<<<<<<<< // - /// Retrive the 32-bit uint at the specified BIT index. - uint32_t GetUIntAtBit(size_t index) { - // @CAO Need proper assert for non-32-size bit fields! - // emp_assert(index < num_bits); - const size_t field_id = FieldID(index); - const size_t pos_id = FieldPos(index); - if (pos_id == 0) return (uint32_t) bit_set[field_id]; - const size_t NUM_FIELDS = NumFields(); - const uint32_t part1 = (uint32_t) (bit_set[field_id] >> pos_id); - const uint32_t part2 = - (uint32_t)((field_id+1 < NUM_FIELDS) ? bit_set[field_id+1] << (FIELD_BITS-pos_id) : 0); - return part1 | part2; - } + /// Automatically convert BitVector to other vector types. + template operator emp::vector(); - /// Retrieve the specified number of bits (stored in the field type) at the target bit index. - template - field_t GetValueAtBit(size_t index) { - // @CAO This function needs to be generalized to return more then sizeof(field_t)*8 bits. - static_assert(OUT_BITS <= sizeof(field_t)*8, "Requesting too many bits to fit in a UInt"); - return GetUIntAtBit(index) & emp::MaskLow(OUT_BITS); - } + /// Casting a bit array to bool identifies if ANY bits are set to 1. + explicit operator bool() const { return Any(); } - /// Return true if ANY bits are set to 1, otherwise return false. - bool Any() const { - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; i++) { - if (bit_set[i]) return true; - } - return false; - } - /// Return true if NO bits are set to 1, otherwise return false. - bool None() const { return !Any(); } + // >>>>>>>>>> Access Groups of bits <<<<<<<<<< // - /// Return true if ALL bits are set to 1, otherwise return false. - bool All() const { return (~(*this)).None(); } + /// Retrive the byte at the specified byte index. + [[nodiscard]] uint8_t GetByte(size_t index) const; - /// Casting a bit array to bool identifies if ANY bits are set to 1. - explicit operator bool() const { return Any(); } + /// Get a read-only view into the internal array used by BitVector. + /// @return Read-only span of BitVector's bytes. + [[nodiscard]] std::span GetBytes() const; - /// Const index operator -- return the bit at the specified position. - bool operator[](size_t index) const { return Get(index); } + /// Get a read-only pointer to the internal array used by BitVector. + /// (note that bits are NOT in order at the byte level!) + /// @return Read-only pointer to BitVector's bytes. + emp::Ptr RawBytes() const { return BytePtr(); } - /// Index operator -- return a proxy to the bit at the specified position so it can be an lvalue. - BitProxy operator[](size_t index) { return BitProxy(*this, index); } + /// Update the byte at the specified byte index. + void SetByte(size_t index, uint8_t value); - /// Set all bits to 0. - void Clear() { - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = 0U; - } + /// Get the overall value of this BitVector, using a uint encoding, but including all bits + /// and returning the value as a double. + [[nodiscard]] double GetValue() const; - /// Set all bits to 1. - void SetAll() { - const size_t NUM_FIELDS = NumFields(); - constexpr field_t all0 = 0; - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = ~all0; - if (LastBitID() > 0) { bit_set[NUM_FIELDS - 1] &= emp::MaskLow(LastBitID()); } - } + /// Return a span with all fields in order. + std::span FieldSpan() { return std::span(bits.Raw(), NumFields()); } - /// Regular print function (from most significant bit to least) - void Print(std::ostream & out=std::cout) const { - for (size_t i = num_bits; i > 0; --i) out << Get(i-1); - } + /// Get specified type at a given index (in steps of that type size) + template + [[nodiscard]] T GetValueAtIndex(const size_t index) const; - /// Print a space between each field (or other provided spacer) - void PrintFields(std::ostream & out=std::cout, const std::string & spacer=" ") const { - for (size_t i = num_bits; i > 0; i--) { - out << Get(i-1); - if (i % FIELD_BITS == 0) out << spacer; - } - } + // Retrieve the 8-bit uint from the specified uint index. + [[nodiscard]] uint8_t GetUInt8(size_t index) const { return GetValueAtIndex(index); } - /// Print from smallest bit position to largest. - void PrintArray(std::ostream & out=std::cout) const { - for (size_t i = 0; i < num_bits; i++) out << Get(i); - } + // Retrieve the 16-bit uint from the specified uint index. + [[nodiscard]] uint16_t GetUInt16(size_t index) const { return GetValueAtIndex(index); } - /// Print the positions of all one bits, spaces are the default separator. - void PrintOneIDs(std::ostream & out=std::cout, const std::string & spacer=" ") const { - for (size_t i = 0; i < num_bits; i++) { if (Get(i)) out << i << spacer; } - } + // Retrieve the 32-bit uint from the specified uint index. + [[nodiscard]] uint32_t GetUInt32(size_t index) const { return GetValueAtIndex(index); } + + // Retrieve the 64-bit uint from the specified uint index. + [[nodiscard]] uint64_t GetUInt64(size_t index) const { return GetValueAtIndex(index); } - /// Print the ones in a range format. E.g., 2-5,7,10-15 - void PrintAsRange(std::ostream & out=std::cout, - const std::string & spacer=",", - const std::string & ranger="-") const - { - emp::vector ones = GetOnes(); + // By default, retrieve the 32-bit uint from the specified uint index. + [[nodiscard]] uint32_t GetUInt(size_t index) const { return GetUInt32(index); } - for (size_t pos = 0; pos < ones.size(); pos++) { - if (pos) out << spacer; - size_t start = ones[pos]; - while (pos+1 < ones.size() && ones[pos+1] == ones[pos]+1) pos++; - size_t end = ones[pos]; + /// Set specified type at a given index (in steps of that type size) + template void SetValueAtIndex(const size_t index, T value); - out << start; - if (start != end) out << ranger << end; - } - } + /// Update the 8-bit uint at the specified uint index. + void SetUInt8(const size_t index, uint8_t value) { SetValueAtIndex(index, value); } - /// Count 1's by looping through once for each bit equal to 1 - size_t CountOnes_Sparse() const { - const size_t NUM_FIELDS = NumFields(); - size_t bit_count = 0; - for (size_t i = 0; i < NUM_FIELDS; i++) { - field_t cur_field = bit_set[i]; - while (cur_field) { - cur_field &= (cur_field-1); // Peel off a single 1. - bit_count++; // And increment the counter - } - } - return bit_count; - } - // TODO: see https://arxiv.org/pdf/1611.07612.pdf for faster pop counts - size_t CountOnes_Mixed() const { - const field_t NUM_FIELDS = (1 + ((num_bits - 1) / FIELD_BITS)); - size_t bit_count = 0; - for (size_t i = 0; i < NUM_FIELDS; i++) { - // when compiling with -O3 and -msse4.2, this is the fastest population count method. - std::bitset std_bs(bit_set[i]); - bit_count += std_bs.count(); - } - - return bit_count; - } + /// Update the 16-bit uint at the specified uint index. + void SetUInt16(const size_t index, uint16_t value) { SetValueAtIndex(index, value); } - /// Count the number of ones in the BitVector. - size_t CountOnes() const { return CountOnes_Mixed(); } + /// Update the 32-bit uint at the specified uint index. + void SetUInt32(const size_t index, uint32_t value) { SetValueAtIndex(index, value); } - /// Count the number of zeros in the BitVector. - size_t CountZeros() const { return GetSize() - CountOnes(); } + /// Update the 64-bit uint at the specified uint index. + void SetUInt64(const size_t index, uint64_t value) { SetValueAtIndex(index, value); } - /// Return the position of the first one; return -1 if no ones in vector. - int FindBit() const { - const size_t NUM_FIELDS = NumFields(); - size_t field_id = 0; - while (field_id < NUM_FIELDS && bit_set[field_id]==0) field_id++; - return (field_id < NUM_FIELDS) ? - (int) (find_bit(bit_set[field_id]) + (field_id * FIELD_BITS)) : -1; - } + /// By default, update the 32-bit uint at the specified uint index. + void SetUInt(const size_t index, uint32_t value) { SetUInt32(index, value); } - /// Return the position of the first one and change it to a zero. Return -1 if no ones. - int PopBit() { - const size_t NUM_FIELDS = NumFields(); - size_t field_id = 0; - while (field_id < NUM_FIELDS && bit_set[field_id]==0) field_id++; - if (field_id == NUM_FIELDS) return -1; // Failed to find bit! - - const size_t pos_found = find_bit(bit_set[field_id]); - constexpr field_t val_one = 1; - bit_set[field_id] &= ~(val_one << pos_found); - return (int) (pos_found + (field_id * FIELD_BITS)); - } - /** - * Pop the last bit in the vector. - * - * @return value of the popped bit. - */ - bool PopBack() { - const bool val = Get(num_bits-1); - Set(num_bits-1, 0); - Resize(num_bits - 1); - return val; - } + /// Get specified type starting at a given BIT position. + template + [[nodiscard]] T GetValueAtBit(const size_t index) const; - /** - * Push given bit(s) onto the vector. - * - * @param bit value of bit to be pushed. - * @param num number of bits to be pushed. - */ - void PushBack(const bool bit=true, const size_t num=1) { - Resize(num_bits + num); - for (size_t i=0; i < num; i++) - Set(num_bits-1, bit); - } + // Retrieve the 8-bit uint from the specified uint index. + [[nodiscard]] uint8_t GetUInt8AtBit(size_t index) const { return GetValueAtBit(index); } - /** - * Zero out bits lower than or at index. - * - * @param index location to end the zeroing out. - */ - void MaskHigh(const size_t index) { - const size_t field_id = FieldID(index); - for (size_t i = 0; i < field_id; i++) - bit_set[i] = 0; - if (index >= 0) - bit_set[field_id] &= (emp::MaskLow((num_bits - index - 1) % FIELD_BITS) << (index + 1)); - } + // Retrieve the 16-bit uint from the specified uint index. + [[nodiscard]] uint16_t GetUInt16AtBit(size_t index) const { return GetValueAtBit(index); } - /** - * Zero out bits higher than or at index. - * - * @param index location to start the zeroing out. - */ - void MaskLow(const size_t index) { - const size_t field_id = FieldID(index); - for (size_t i = field_id + 1; i < NumFields(); i++) - bit_set[i] = 0; - if (index < num_bits) - bit_set[field_id] &= emp::MaskLow(index % FIELD_BITS); - } + // Retrieve the 32-bit uint from the specified uint index. + [[nodiscard]] uint32_t GetUInt32AtBit(size_t index) const { return GetValueAtBit(index); } + + // Retrieve the 64-bit uint from the specified uint index. + [[nodiscard]] uint64_t GetUInt64AtBit(size_t index) const { return GetValueAtBit(index); } - /** - * Insert bit(s) into any index of vector using bit magic. - * Blog post on implementation reasoning: https://devolab.org/?p=2249 - * - * @param index location to insert bit(s). - * @param val value of bit(s) to insert. - * @param num number of bits to insert, default 1. - */ - void Insert(const size_t index, const bool val=true, const size_t num=1) { - Resize(num_bits + num); + // By default, retrieve the 32-bit uint from the specified uint index. + [[nodiscard]] uint32_t GetUIntAtBit(size_t index) const { return GetUInt32AtBit(index); } - thread_local BitVector temp{}; // use thread_local to prevent reallocation - temp = *this; - // mask high and shift left: 101101 -> 1010000 - if (index > 0) this->MaskHigh(index + num - 2); - *this <<= num; + template void SetValueAtBit(const size_t index, T value); - // mask low and | together 101101 -> 000101 ->1010101 - temp.MaskLow(index); - *this |= temp; + /// Update the 8-bit uint at the specified uint index. + void SetUInt8AtBit(const size_t index, uint8_t value) { SetValueAtBit(index, value); } - // set specified bits: 1010101 -> 101_101 - for (size_t i = index; i < index + num; i++) this->Set(i, val); + /// Update the 16-bit uint at the specified uint index. + void SetUInt16AtBit(const size_t index, uint16_t value) { SetValueAtBit(index, value); } - } + /// Update the 32-bit uint at the specified uint index. + void SetUInt32AtBit(const size_t index, uint32_t value) { SetValueAtBit(index, value); } - /** - * Delete bits from any index in a vector. - * - * TODO: consider a bit magic approach here. - * - * @param index location to delete bit(s). - * @param num number of bits to delete, default 1. - */ - void Delete(const size_t index, const size_t num=1) { - for (size_t bit = index; bit < num_bits - num; bit++) { - Set(bit, Get(bit + num)); - } - Resize(num_bits - num); - } + /// Update the 64-bit uint at the specified uint index. + void SetUInt64AtBit(const size_t index, uint64_t value) { SetValueAtBit(index, value); } + + /// By default, update the 32-bit uint at the specified uint index. + void SetUIntAtBit(const size_t index, uint32_t value) { SetUInt32AtBit(index, value); } + + + // >>>>>>>>>> Other Analyses <<<<<<<<<< // + + /// A simple hash function for bit vectors. + [[nodiscard]] std::size_t Hash(size_t start_field=0) const; + + /// Count the number of ones in the BitVector. + [[nodiscard]] size_t CountOnes() const; + + /// Faster counting of ones for very sparse bit vectors. + [[nodiscard]] size_t CountOnes_Sparse() const; + + /// Count the number of zeros in the BitVector. + [[nodiscard]] size_t CountZeros() const { return GetSize() - CountOnes(); } + + /// Pop the last bit in the vector. + /// @return value of the popped bit. + bool PopBack(); + + /// Push given bit(s) onto the back of a vector. + /// @param bit value of bit to be pushed. + /// @param num number of bits to be pushed. + void PushBack(const bool bit=true, const size_t num=1); + + /// Insert bit(s) into any index of vector using bit magic. + /// Blog post on implementation reasoning: https://devolab.org/?p=2249 + /// @param index location to insert bit(s). + /// @param val value of bit(s) to insert. + /// @param num number of bits to insert, default 1. + void Insert(const size_t index, const bool val=true, const size_t num=1); + + /// Delete bits from any index in a vector. + /// TODO: consider a bit magic approach here. + /// @param index location to delete bit(s). + /// @param num number of bits to delete, default 1. + void Delete(const size_t index, const size_t num=1); + + /// Return the position of the first one; return -1 if no ones in vector. + [[nodiscard]] int FindOne() const; + + /// Deprecated: Return the position of the first one; return -1 if no ones in vector. + [[deprecated("Renamed to more acurate FindOne()")]] + [[nodiscard]] int FindBit() const { return FindOne(); } /// Return the position of the first one after start_pos; return -1 if no ones in vector. /// You can loop through all 1-bit positions of a BitVector "bv" with: /// - /// for (int pos = bv.FindBit(); pos >= 0; pos = bv.FindBit(pos+1)) { ... } - - int FindBit(const size_t start_pos) const { - if (start_pos >= num_bits) return -1; - size_t field_id = FieldID(start_pos); // What field do we start in? - const size_t field_pos = FieldPos(start_pos); // What position in that field? - if (field_pos && (bit_set[field_id] & ~(emp::MaskLow(field_pos)))) { // First field hit! - return (int) (find_bit(bit_set[field_id] & ~(emp::MaskLow(field_pos))) + - field_id * FIELD_BITS); - } + /// for (int pos = bv.FindOne(); pos >= 0; pos = bv.FindOne(pos+1)) { ... } + /// + [[nodiscard]] int FindOne(const size_t start_pos) const; - // Search other fields... - const size_t NUM_FIELDS = NumFields(); - if (field_pos) field_id++; - while (field_id < NUM_FIELDS && bit_set[field_id]==0) field_id++; - return (field_id < NUM_FIELDS) ? - (int) (find_bit(bit_set[field_id]) + (field_id * FIELD_BITS)) : -1; - } + /// Deprecated version of FindOne(). + [[deprecated("Renamed to more acurate FindOne(start_pos)")]] + [[nodiscard]] int FindBit(const size_t start_pos) const; + + /// Find the most-significant set-bit. + [[nodiscard]] int FindMaxOne() const; + + /// Return the position of the first one and change it to a zero. Return -1 if no ones. + int PopOne(); + + /// Deprecated version of PopOne(). + [[deprecated("Renamed to more acurate PopOne()")]] + int PopBit() { return PopOne(); } /// Return positions of all ones. - emp::vector GetOnes() const { - // @CAO -- There are probably better ways to do this with bit tricks. - emp::vector out_set(CountOnes()); - size_t cur_pos = 0; - for (size_t i = 0; i < num_bits; i++) { - if (Get(i)) out_set[cur_pos++] = i; - } - return out_set; - } + [[nodiscard]] emp::vector GetOnes() const; - /// Perform a Boolean NOT on this BitVector and return the result. - BitVector NOT() const { - const size_t NUM_FIELDS = NumFields(); - BitVector out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = ~bit_set[i]; - if (LastBitID() > 0) out_set.bit_set[NUM_FIELDS - 1] &= emp::MaskLow(LastBitID()); - return out_set; - } + /// Find the length of the longest continuous series of ones. + [[nodiscard]] size_t LongestSegmentOnes() const; - /// Perform a Boolean AND on this BitVector and return the result. - BitVector AND(const BitVector & set2) const { - const size_t NUM_FIELDS = NumFields(); - BitVector out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = bit_set[i] & set2.bit_set[i]; - return out_set; - } + /// Return true if any ones are in common with another BitVector. + [[nodiscard]] bool HasOverlap(const BitVector & in) const; - /// Perform a Boolean OR on this BitVector and return the result. - BitVector OR(const BitVector & set2) const { - const size_t NUM_FIELDS = NumFields(); - BitVector out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = bit_set[i] | set2.bit_set[i]; - return out_set; - } - /// Perform a Boolean NAND on this BitVector and return the result. - BitVector NAND(const BitVector & set2) const { - const size_t NUM_FIELDS = NumFields(); - BitVector out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = ~(bit_set[i] & set2.bit_set[i]); - if (LastBitID() > 0) out_set.bit_set[NUM_FIELDS - 1] &= emp::MaskLow(LastBitID()); - return out_set; - } + // >>>>>>>>>> Print/String Functions <<<<<<<<<< // - /// Perform a Boolean NOR on this BitVector and return the result. - BitVector NOR(const BitVector & set2) const { - const size_t NUM_FIELDS = NumFields(); - BitVector out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = ~(bit_set[i] | set2.bit_set[i]); - if (LastBitID() > 0) out_set.bit_set[NUM_FIELDS - 1] &= emp::MaskLow(LastBitID()); - return out_set; - } + /// Convert a specified bit to a character. + [[nodiscard]] char GetAsChar(size_t id) const { return Get(id) ? '1' : '0'; } - /// Perform a Boolean XOR on this BitVector and return the result. - BitVector XOR(const BitVector & set2) const { - const size_t NUM_FIELDS = NumFields(); - BitVector out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = bit_set[i] ^ set2.bit_set[i]; - return out_set; - } + /// Convert this BitVector to a vector string [index 0 on left] + [[nodiscard]] std::string ToString() const; - /// Perform a Boolean EQU on this BitVector and return the result. - BitVector EQU(const BitVector & set2) const { - const size_t NUM_FIELDS = NumFields(); - BitVector out_set(*this); - for (size_t i = 0; i < NUM_FIELDS; i++) out_set.bit_set[i] = ~(bit_set[i] ^ set2.bit_set[i]); - if (LastBitID() > 0) out_set.bit_set[NUM_FIELDS - 1] &= emp::MaskLow(LastBitID()); - return out_set; + /// Convert this BitVector to a numerical string [index 0 on right] + [[nodiscard]] std::string ToBinaryString() const; + + /// Convert this BitVector to a series of IDs + [[nodiscard]] std::string ToIDString(const std::string & spacer=" ") const; + + /// Convert this BitVector to a series of IDs with ranges condensed. + [[nodiscard]] std::string ToRangeString(const std::string & spacer=",", + const std::string & ranger="-") const; + + /// Regular print function (from least significant bit to most) + void Print(std::ostream & out=std::cout) const { out << ToString(); } + + /// Numerical print function (from most significant bit to least) + void PrintBinary(std::ostream & out=std::cout) const { out << ToBinaryString(); } + + /// Print from smallest bit position to largest. + void PrintArray(std::ostream & out=std::cout) const { out << ToString(); } + + /// Print a space between each field (or other provided spacer) + void PrintFields(std::ostream & out=std::cout, const std::string & spacer=" ") const; + + /// Print out details about the internals of the BitVector. + void PrintDebug(std::ostream & out=std::cout) const; + + /// Print the positions of all one bits, spaces are the default separator. + void PrintOneIDs(std::ostream & out=std::cout, const std::string & spacer=" ") const; + + /// Print the ones in a range format. E.g., 2-5,7,10-15 + void PrintAsRange(std::ostream & out=std::cout, + const std::string & spacer=",", + const std::string & ranger="-") const; + + /// Overload ostream operator to return Print. + friend std::ostream& operator<<(std::ostream &out, const BitVector & bv) { + bv.Print(out); + return out; } + // >>>>>>>>>> Boolean Logic and Shifting Operations <<<<<<<<<< // + /// Perform a Boolean NOT with this BitVector, store result here, and return this object. - BitVector & NOT_SELF() { - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = ~bit_set[i]; - if (LastBitID() > 0) bit_set[NUM_FIELDS - 1] &= emp::MaskLow(LastBitID()); - return *this; - } + BitVector & NOT_SELF(); /// Perform a Boolean AND with this BitVector, store result here, and return this object. - BitVector & AND_SELF(const BitVector & set2) { - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = bit_set[i] & set2.bit_set[i]; - return *this; - } + BitVector & AND_SELF(const BitVector & bv2); /// Perform a Boolean OR with this BitVector, store result here, and return this object. - BitVector & OR_SELF(const BitVector & set2) { - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = bit_set[i] | set2.bit_set[i]; - return *this; - } + BitVector & OR_SELF(const BitVector & bv2); /// Perform a Boolean NAND with this BitVector, store result here, and return this object. - BitVector & NAND_SELF(const BitVector & set2) { - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = ~(bit_set[i] & set2.bit_set[i]); - if (LastBitID() > 0) bit_set[NUM_FIELDS - 1] &= emp::MaskLow(LastBitID()); - return *this; - } + BitVector & NAND_SELF(const BitVector & bv2); /// Perform a Boolean NOR with this BitVector, store result here, and return this object. - BitVector & NOR_SELF(const BitVector & set2) { - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = ~(bit_set[i] | set2.bit_set[i]); - if (LastBitID() > 0) bit_set[NUM_FIELDS - 1] &= emp::MaskLow(LastBitID()); - return *this; - } + BitVector & NOR_SELF(const BitVector & bv2); /// Perform a Boolean XOR with this BitVector, store result here, and return this object. - BitVector & XOR_SELF(const BitVector & set2) { - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = bit_set[i] ^ set2.bit_set[i]; - return *this; - } + BitVector & XOR_SELF(const BitVector & bv2); /// Perform a Boolean EQU with this BitVector, store result here, and return this object. - BitVector & EQU_SELF(const BitVector & set2) { - const size_t NUM_FIELDS = NumFields(); - for (size_t i = 0; i < NUM_FIELDS; i++) bit_set[i] = ~(bit_set[i] ^ set2.bit_set[i]); - if (LastBitID() > 0) bit_set[NUM_FIELDS - 1] &= emp::MaskLow(LastBitID()); - return *this; - } + BitVector & EQU_SELF(const BitVector & bv2); + + + /// Perform a Boolean NOT on this BitVector and return the result. + [[nodiscard]] BitVector NOT() const { return BitVector(*this).NOT_SELF(); } + + /// Perform a Boolean AND on this BitVector and return the result. + [[nodiscard]] BitVector AND(const BitVector & bv2) const { return BitVector(*this).AND_SELF(bv2); } + + /// Perform a Boolean OR on this BitVector and return the result. + [[nodiscard]] BitVector OR(const BitVector & bv2) const { return BitVector(*this).OR_SELF(bv2); } + + /// Perform a Boolean NAND on this BitVector and return the result. + [[nodiscard]] BitVector NAND(const BitVector & bv2) const { return BitVector(*this).NAND_SELF(bv2); } + + /// Perform a Boolean NOR on this BitVector and return the result. + [[nodiscard]] BitVector NOR(const BitVector & bv2) const { return BitVector(*this).NOR_SELF(bv2); } + + /// Perform a Boolean XOR on this BitVector and return the result. + [[nodiscard]] BitVector XOR(const BitVector & bv2) const { return BitVector(*this).XOR_SELF(bv2); } + + /// Perform a Boolean EQU on this BitVector and return the result. + [[nodiscard]] BitVector EQU(const BitVector & bv2) const { return BitVector(*this).EQU_SELF(bv2); } + /// Positive shifts go left and negative go right (0 does nothing); return result. - BitVector SHIFT(const int shift_size) const { - BitVector out_set(*this); - if (shift_size > 0) out_set.ShiftRight((size_t) shift_size); - else if (shift_size < 0) out_set.ShiftLeft((size_t) -shift_size); - return out_set; - } + [[nodiscard]] BitVector SHIFT(const int shift_size) const; /// Positive shifts go left and negative go right; store result here, and return this object. - BitVector & SHIFT_SELF(const int shift_size) { - if (shift_size > 0) ShiftRight((size_t) shift_size); - else if (shift_size < 0) ShiftLeft((size_t) -shift_size); - return *this; - } + BitVector & SHIFT_SELF(const int shift_size); + + /// Reverse the order of bits in the bitset + BitVector & REVERSE_SELF(); + + /// Reverse order of bits in the bitset. + [[nodiscard]] BitVector REVERSE() const; + + /// Positive rotates go left and negative rotates go left (0 does nothing); + /// return result. + [[nodiscard]] BitVector ROTATE(const int rotate_size) const; + + /// Positive rotates go right and negative rotates go left (0 does nothing); + /// store result here, and return this object. + BitVector & ROTATE_SELF(const int rotate_size); + + /// Helper: call ROTATE with negative number instead + template + BitVector & ROTL_SELF(); + + /// Helper for calling ROTATE with positive number + template + BitVector & ROTR_SELF(); + + /// Addition of two BitVectors. + /// Wraps if it overflows. + /// Returns result. + [[nodiscard]] BitVector ADD(const BitVector & set2) const; + + /// Addition of two BitVectors. + /// Wraps if it overflows. + /// Returns this object. + BitVector & ADD_SELF(const BitVector & set2); + + /// Subtraction of two BitVectors. + /// Wraps around if it underflows. + /// Returns result. + [[nodiscard]] BitVector SUB(const BitVector & set2) const; + + /// Subtraction of two BitVectors. + /// Wraps if it underflows. + /// Returns this object. + BitVector & SUB_SELF(const BitVector & set2); /// Operator bitwise NOT... - BitVector operator~() const { return NOT(); } + [[nodiscard]] inline BitVector operator~() const { return NOT(); } /// Operator bitwise AND... - BitVector operator&(const BitVector & ar2) const { return AND(ar2); } + [[nodiscard]] inline BitVector operator&(const BitVector & ar2) const { return AND(ar2); } /// Operator bitwise OR... - BitVector operator|(const BitVector & ar2) const { return OR(ar2); } + [[nodiscard]] inline BitVector operator|(const BitVector & ar2) const { return OR(ar2); } /// Operator bitwise XOR... - BitVector operator^(const BitVector & ar2) const { return XOR(ar2); } + [[nodiscard]] inline BitVector operator^(const BitVector & ar2) const { return XOR(ar2); } /// Operator shift left... - inline BitVector operator<<(const size_t shift_size) const { return SHIFT(-(int)shift_size); } + [[nodiscard]] inline BitVector operator<<(const size_t shift_size) const { return SHIFT(-(int)shift_size); } /// Operator shift right... - inline BitVector operator>>(const size_t shift_size) const { return SHIFT((int)shift_size); } + [[nodiscard]] inline BitVector operator>>(const size_t shift_size) const { return SHIFT((int)shift_size); } /// Compound operator bitwise AND... - const BitVector & operator&=(const BitVector & ar2) { return AND_SELF(ar2); } + BitVector & operator&=(const BitVector & ar2) { return AND_SELF(ar2); } /// Compound operator bitwise OR... - const BitVector & operator|=(const BitVector & ar2) { return OR_SELF(ar2); } + BitVector & operator|=(const BitVector & ar2) { return OR_SELF(ar2); } /// Compound operator bitwise XOR... - const BitVector & operator^=(const BitVector & ar2) { return XOR_SELF(ar2); } + BitVector & operator^=(const BitVector & ar2) { return XOR_SELF(ar2); } /// Compound operator for shift left... - const BitVector & operator<<=(const size_t shift_size) { return SHIFT_SELF(-(int)shift_size); } + BitVector & operator<<=(const size_t shift_size) { return SHIFT_SELF(-(int)shift_size); } /// Compound operator for shift right... - const BitVector & operator>>=(const size_t shift_size) { return SHIFT_SELF((int)shift_size); } + BitVector & operator>>=(const size_t shift_size) { return SHIFT_SELF((int)shift_size); } - /// Function to allow drop-in replacement with std::vector. - size_t size() const { return num_bits; } + // >>>>>>>>>> Standard Library Compatability <<<<<<<<<< // + // A set of functions to allow drop-in replacement with std::bitset. - /// Function to allow drop-in replacement with std::vector. + [[nodiscard]] size_t size() const { return num_bits; } void resize(std::size_t new_size) { Resize(new_size); } + [[nodiscard]] bool all() const { return All(); } + [[nodiscard]] bool any() const { return Any(); } + [[nodiscard]] bool none() const { return !Any(); } + size_t count() const { return CountOnes(); } + BitVector & flip() { return Toggle(); } + BitVector & flip(size_t pos) { return Toggle(pos); } + BitVector & flip(size_t start, size_t end) { return Toggle(start, end); } + void reset() { Clear(); } + void reset(size_t id) { Set(id, false); } + void set() { SetAll(); } + void set(size_t id) { Set(id); } + [[nodiscard]] bool test(size_t index) const { return Get(index); } + }; - /// Function to allow drop-in replacement with std::vector. - bool all() const { return All(); } - /// Function to allow drop-in replacement with std::vector. - bool any() const { return Any(); } - /// Function to allow drop-in replacement with std::vector. - bool none() const { return !Any(); } + // ------------------------ Implementations for Internal Functions ------------------------ - /// Function to allow drop-in replacement with std::vector. - size_t count() const { return CountOnes_Mixed(); } - }; + void BitVector::RawCopy(const Ptr in) { + #ifdef EMP_TRACK_MEM + emp_assert(in.IsNull() == false); + emp_assert(bits.DebugIsArray() && in.DebugIsArray()); + emp_assert(bits.DebugGetArrayBytes() == in.DebugGetArrayBytes(), + bits.DebugGetArrayBytes(), in.DebugGetArrayBytes()); + #endif -} + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = in[i]; + } + + // Move bits from one position in the genome to another; leave old positions unchanged. + // @CAO: Can speed up by focusing only on the moved fields (i.e., don't shift unused bits). + void BitVector::RawCopy(const size_t from_start, const size_t from_stop, const size_t to) { + emp_assert(from_start <= from_stop); // Must move legal region. + emp_assert(from_stop <= num_bits); // Cannot move from past end. + emp_assert(to <= num_bits); // Must move to somewhere legal. + + // If nothing to copy OR already in place, stop right there. + if (from_start == from_stop || from_start == to) return; + + const size_t move_size = from_stop - from_start; // How bit is the chunk to move? + const size_t to_stop = Min(to+move_size, num_bits); // Where is the end to move it to? + const int shift = (int) from_start - (int) to; // How far will the moved piece shift? + BitVector move_bits(*this); // Vector to hold moved bits. + move_bits.SHIFT_SELF(shift); // Put the moved bits in place. + Clear(to, to_stop); // Make room for the moved bits. + move_bits.Clear(0, to); // Clear everything BEFORE moved bits. + move_bits.Clear(to_stop, num_bits); // Clear everything AFTER moved bits. + OR_SELF(move_bits); // Merge bitstrings together. + } + + template + BitVector & BitVector::ApplyRange(const FUN_T & fun, size_t start, size_t stop) { + if (start == stop) return *this; // Empty range. + + emp_assert(start <= stop, start, stop, num_bits); // Start cannot be after stop. + emp_assert(stop <= num_bits, stop, num_bits); // Stop cannot be past the end of the bits + const size_t start_pos = FieldPos(start); // Identify the start position WITHIN a bit field. + const size_t stop_pos = FieldPos(stop); // Identify the stop position WITHIN a bit field. + size_t start_field = FieldID(start); // Ideftify WHICH bit field we're starting in. + const size_t stop_field = FieldID(stop-1); // Identify the last field where we actually make a change. + + // If the start field and stop field are the same, mask off the middle. + if (start_field == stop_field) { + const size_t apply_bits = stop - start; // How many bits to change? + const field_t mask = MaskLow(apply_bits) << start_pos; // Target change bits with a mask. + field_t & target = bits[start_field]; // Isolate the field to change. + target = (target & ~mask) | (fun(target) & mask); // Update targeted bits! + } + + // Otherwise mask the ends and fully modify the chunks in between. + else { + // If we're only using a portions of start field, mask it and setup. + if (start_pos != 0) { + const size_t start_bits = FIELD_BITS - start_pos; // How many bits in start field? + const field_t mask = MaskLow(start_bits) << start_pos; // Target start bits with a mask. + field_t & target = bits[start_field]; // Isolate the field to change. + target = (target & ~mask) | (fun(target) & mask); // Update targeted bits! + start_field++; // Done with this field; move to the next. + } + + // Middle fields + for (size_t cur_field = start_field; cur_field < stop_field; cur_field++) { + bits[cur_field] = fun(bits[cur_field]); + } + + // Set portions of stop field + const field_t mask = MaskLow(stop_pos); + field_t & target = bits[stop_field]; // Isolate the field to change. + target = (target & ~mask) | (fun(target) & mask); // Update targeted bits! + } + + return *this; + } + + void BitVector::ShiftLeft(const size_t shift_size) { + // If we are shifting out of range, clear the bits and stop. + if (shift_size >= num_bits) { Clear(); return; } + + // If we have only a single field, this operation can be quick. + if (NumFields() == 1) { + (bits[0] <<= shift_size) &= EndMask(); + return; + } + + const size_t field_shift = shift_size / FIELD_BITS; + const size_t bit_shift = shift_size % FIELD_BITS; + const size_t bit_overflow = FIELD_BITS - bit_shift; + + // Loop through each field, from L to R, and update it. + if (field_shift) { + for (size_t i = LastField(); i >= field_shift; --i) { + bits[i] = bits[i - field_shift]; + } + for (size_t i = field_shift; i > 0; --i) bits[i-1] = 0; + } + + // account for bit_shift + if (bit_shift) { + for (size_t i = LastField() ; i > field_shift; --i) { + bits[i] <<= bit_shift; + bits[i] |= (bits[i-1] >> bit_overflow); + } + // Handle final field (field_shift position) + bits[field_shift] <<= bit_shift; + } + + // Mask out any bits that have left-shifted away + ClearExcessBits(); + } + + void BitVector::ShiftRight(const size_t shift_size) { + // If we are shifting out of range, clear the bits and stop. + if (shift_size >= num_bits) { Clear(); return; } + + // If we have only a single field, this operation can be quick. + if (NumFields() == 1) { + bits[0] >>= shift_size; + return; + } + + const size_t field_shift = shift_size / FIELD_BITS; + const size_t bit_shift = shift_size % FIELD_BITS; + const size_t bit_overflow = FIELD_BITS - bit_shift; + const size_t NUM_FIELDS = NumFields(); + const size_t field_shift2 = NUM_FIELDS - field_shift; + + // account for field_shift + if (field_shift) { + for (size_t i = 0; i < field_shift2; ++i) { + bits[i] = bits[i + field_shift]; + } + for (size_t i = field_shift2; i < NUM_FIELDS; i++) bits[i] = FIELD_0; + } + + // account for bit_shift + if (bit_shift) { + for (size_t i = 0; i < (field_shift2 - 1); ++i) { + bits[i] >>= bit_shift; + bits[i] |= (bits[i+1] << bit_overflow); + } + bits[field_shift2 - 1] >>= bit_shift; + } + } + + /// Helper: call ROTATE with negative number + void BitVector::RotateLeft(const size_t shift_size_raw) { + if (num_bits == 0) return; // Nothing to rotate in an empty BitVector. + + const field_t shift_size = shift_size_raw % num_bits; + const size_t NUM_FIELDS = NumFields(); + + // Use different approaches based on BitVector size + if (NUM_FIELDS == 1) { + // Special case: for exactly one field_T, try to go low level. + // Adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c + field_t & n = bits[0]; + size_t c = shift_size; + + // Mask necessary to suprress shift count overflow warnings. + c &= FIELD_LOG2_MASK; + n = (n<>( (-(c+FIELD_BITS-num_bits)) & FIELD_LOG2_MASK )); + } + else if (NUM_FIELDS < 32) { // For small BitVectors, shifting L/R and ORing is faster. + emp::BitVector dup(*this); + dup.ShiftLeft(shift_size); + ShiftRight(num_bits - shift_size); + OR_SELF(dup); + } + else { // For big BitVectors, manual rotating is fater + // Note: we already modded shift_size by num_bits, so no need to mod by FIELD_SIZE + const int field_shift = ( shift_size + EndGap() ) / FIELD_BITS; + + // If we field shift, we need to shift bits by (FIELD_BITS - NumEndBits()) + // to account for the filler that gets pulled out of the middle + const int bit_shift = NumEndBits() && (shift_size + field_shift ? EndGap() : 0) % FIELD_BITS; + const int bit_overflow = FIELD_BITS - bit_shift; + + // if rotating more than field capacity, we need to rotate fields + auto field_span = FieldSpan(); + std::rotate( + field_span.rbegin(), + field_span.rbegin()+field_shift, + field_span.rend() + ); + + // if necessary, shift filler bits out of the middle + if (NumEndBits()) { + const int filler_idx = (LastField() + field_shift) % NUM_FIELDS; + for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { + bits[i-1] |= bits[i] << NumEndBits(); + bits[i] >>= (FIELD_BITS - NumEndBits()); + } + } + + // account for bit_shift + if (bit_shift) { + + const field_t keystone = NumEndBits() ? ( + (bits[LastField()] << (FIELD_BITS - NumEndBits())) + | (bits[NUM_FIELDS - 2] >> NumEndBits()) + ) : ( + bits[LastField()] + ); + + for (int i = LastField(); i > 0; --i) { + bits[i] <<= bit_shift; + bits[i] |= (bits[i-1] >> bit_overflow); + } + // Handle final field + bits[0] <<= bit_shift; + bits[0] |= keystone >> bit_overflow; + + } + + } + + // Mask out filler bits + ClearExcessBits(); + } + + + /// Helper for calling ROTATE with positive number + void BitVector::RotateRight(const size_t shift_size_raw) { + const size_t shift_size = shift_size_raw % num_bits; + const size_t NUM_FIELDS = NumFields(); + + // use different approaches based on BitVector size + if (NUM_FIELDS == 1) { + // special case: for exactly one field_t, try to go low level + // adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c + + field_t & n = bits[0]; + size_t c = shift_size; + + // mask necessary to suprress shift count overflow warnings + c &= FIELD_LOG2_MASK; + n = (n>>c) | (n<<( (num_bits-c) & FIELD_LOG2_MASK )); + + } else if (NUM_FIELDS < 32) { + // for small BitVectors, shifting L/R and ORing is faster + emp::BitVector dup(*this); + dup.ShiftRight(shift_size); + ShiftLeft(num_bits - shift_size); + OR_SELF(dup); + } else { + // for big BitVectors, manual rotating is fater + + const field_t field_shift = (shift_size / FIELD_BITS) % NUM_FIELDS; + const int bit_shift = shift_size % FIELD_BITS; + const field_t bit_overflow = FIELD_BITS - bit_shift; + + // if rotating more than field capacity, we need to rotate fields + auto field_span = FieldSpan(); + std::rotate( + field_span.begin(), + field_span.begin()+field_shift, + field_span.end() + ); + + // if necessary, shift filler bits out of the middle + if (NumEndBits()) { + const int filler_idx = LastField() - field_shift; + for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { + bits[i-1] |= bits[i] << NumEndBits(); + bits[i] >>= (FIELD_BITS - NumEndBits()); + } + } + + // account for bit_shift + if (bit_shift) { + + const field_t keystone = NumEndBits() ? ( + bits[0] >> (FIELD_BITS - NumEndBits()) + ) : ( + bits[0] + ); + + if (NumEndBits()) { + bits[NUM_FIELDS-1] |= bits[0] << NumEndBits(); + } + + for (size_t i = 0; i < LastField(); ++i) { + bits[i] >>= bit_shift; + bits[i] |= (bits[i+1] << bit_overflow); + } + bits[LastField()] >>= bit_shift; + bits[LastField()] |= keystone << bit_overflow; + } + } + + // Mask out filler bits + ClearExcessBits(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////// + // ---------------------------------------------------------------------------------------- + // --------------------- Implementations of Public Member Functions ----------------------- + // ---------------------------------------------------------------------------------------- + /////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////// + + + // ------------------- Implementations of Constructors and Assignments -------------------- + + /// Build a new BitVector with specified bit count (default 0) and initialization (default 0) + BitVector::BitVector(size_t in_num_bits, bool init_val) : num_bits(in_num_bits), bits(nullptr) { + if (num_bits) bits = NewArrayPtr(NumFields()); + if (init_val) SetAll(); else Clear(); + } + + /// Copy constructor of existing bit field. + BitVector::BitVector(const BitVector & in) : num_bits(in.num_bits), bits(nullptr) { + emp_assert(in.OK()); + + // There is only something to copy if there are a non-zero number of bits! + if (num_bits) { + #ifdef EMP_TRACK_MEM + emp_assert(!in.bits.IsNull() && in.bits.DebugIsArray(), in.bits.IsNull(), in.bits.DebugIsArray()); + #endif + bits = NewArrayPtr(NumFields()); + RawCopy(in.bits); + } + } + + /// Move constructor of existing bit field. + BitVector::BitVector(BitVector && in) : num_bits(in.num_bits), bits(in.bits) { + emp_assert(in.OK()); + + in.bits = nullptr; + in.num_bits = 0; + } + + /// Constructor to generate a BitVector from a std::bitset. + template + BitVector::BitVector(const std::bitset & bitset) : num_bits(NUM_BITS), bits(nullptr) { + if (num_bits) { + bits = NewArrayPtr(NumFields()); + Clear(); + for (size_t i = 0; i < NUM_BITS; i++) if (bitset[i]) Set(i); + } + } + + /// Constructor to generate a BitVector from a string of '0's and '1's. + BitVector::BitVector(const std::string & bitstring) : num_bits(bitstring.size()), bits(nullptr) { + if (num_bits) { + bits = NewArrayPtr(NumFields()); + Clear(); + for (size_t i = 0; i < num_bits; i++) { + if (bitstring[i] != '0') Set(i); + } + } + } + + /// Constructor to generate a random BitVector (with equal prob of 0 or 1). + BitVector::BitVector(size_t in_num_bits, Random & random) + : num_bits(in_num_bits), bits(nullptr) + { + if (num_bits) { + bits = NewArrayPtr(NumFields()); + Clear(); + Randomize(random); + } + } + + /// Constructor to generate a random BitVector with provided prob of 1's. + BitVector::BitVector(size_t in_num_bits, Random & random, const double p1) + : num_bits(in_num_bits), bits(nullptr) + { + if (num_bits) { + bits = NewArrayPtr(NumFields()); + Clear(); + Randomize(random, p1); + } + } + + /// Constructor to generate a random BitVector with provided number of 1's. + BitVector::BitVector(size_t in_num_bits, Random & random, const size_t target_ones) + : num_bits(in_num_bits), bits(nullptr) + { + if (num_bits) { + bits = NewArrayPtr(NumFields()); + Clear(); + ChooseRandom(random, target_ones); + } + } + + /// Initializer list constructor. + template + BitVector::BitVector(const std::initializer_list l) : num_bits(l.size()), bits(nullptr) { + if (num_bits) bits = NewArrayPtr(NumFields()); + + size_t idx = 0; + for (auto i = std::begin(l); i != std::end(l); ++i) Set(idx++, *i); + ClearExcessBits(); + } + + /// Copy, but with a resize. + BitVector::BitVector(const BitVector & in, size_t new_size) : BitVector(in) { + if (num_bits != new_size) Resize(new_size); + } + + /// Destructor + BitVector::~BitVector() { + if (bits) { // A move constructor can make bits == nullptr + bits.DeleteArray(); + bits = nullptr; + } + } + + /// Assignment operator. + BitVector & BitVector::operator=(const BitVector & in) { + emp_assert(in.OK()); + + if (&in == this) return *this; + const size_t in_num_fields = in.NumFields(); + const size_t prev_num_fields = NumFields(); + num_bits = in.num_bits; + + if (in_num_fields != prev_num_fields) { + if (bits) bits.DeleteArray(); + if (num_bits) bits = NewArrayPtr(in_num_fields); + else bits = nullptr; + } + + if (num_bits) RawCopy(in.bits); + + return *this; + } + + /// Move operator. + BitVector & BitVector::operator=(BitVector && in) { + emp_assert(&in != this); // in is an r-value, so this shouldn't be possible... + if (bits) bits.DeleteArray(); // If we already have bits, get rid of them. + num_bits = in.num_bits; // Update the number of bits... + bits = in.bits; // And steal the old memory for what those bits are. + in.bits = nullptr; // Prepare in for deletion without deallocating. + in.num_bits = 0; + + return *this; + } + + /// Assignment operator from a std::bitset. + template + BitVector & BitVector::operator=(const std::bitset & bitset) { + const size_t start_fields = NumFields(); + num_bits = NUM_BITS; + const size_t new_fields = NumFields(); + + // Update the size of internal fields if needed. + if (start_fields != new_fields) { + if (bits) bits.DeleteArray(); // If we already had a bitset, get rid of it. + if constexpr (NUM_BITS > 0) bits = NewArrayPtr(new_fields); + else bits = nullptr; + } + + for (size_t i = 0; i < NUM_BITS; i++) Set(i, bitset[i]); // Copy bits in. + ClearExcessBits(); // Set excess bits to zeros. + + return *this; + } + + /// Assignment operator from a string of '0's and '1's. + BitVector & BitVector::operator=(const std::string & bitstring) { + const size_t start_fields = NumFields(); + num_bits = bitstring.size(); + const size_t new_fields = NumFields(); + + // Update the size of internal fields if needed. + if (start_fields != new_fields) { + if (bits) bits.DeleteArray(); // If we already had a bitset, get rid of it. + if (num_bits) bits = NewArrayPtr(new_fields); + else bits = nullptr; + Clear(); + } + + // If we have bits, copy them in. + if (num_bits) { + for (size_t i = 0; i < num_bits; i++) { + if (bitstring[i] != '0') Set(i); + } + } + + return *this; + } + + + /// Assign from a BitVector of a different size. + // @CAO: Can manually copy to skip unused fields for a speedup. + BitVector & BitVector::Import(const BitVector & from_bv, const size_t from_bit) { + emp_assert(&from_bv != this); + emp_assert(from_bit < from_bv.GetSize()); + + const size_t init_size = GetSize(); + *this = from_bv; + *this >>= from_bit; + Resize(init_size); + + return *this; + } + + /// Convert to a Bitset of a different size. + BitVector BitVector::Export(size_t out_size, size_t start_bit) const { + + BitVector out_bits(out_size); + out_bits.Import(*this, start_bit); + + return out_bits; + } + + bool BitVector::OK() const { + // Do some checking on the bits array ptr to make sure it's value. + if (bits) { +#ifdef EMP_TRACK_MEM + emp_assert(bits.DebugIsArray()); // Must be marked as an array. + emp_assert(bits.OK()); // Pointer must be okay. +#endif + + // If there are end bits, make sure that everything past the last one is clear. + if (NumEndBits()) { + // Make sure final bits are zeroed out. + [[maybe_unused]] field_t excess_bits = bits[LastField()] & ~MaskLow(NumEndBits()); + emp_assert(!excess_bits); + } + } + + // Otherwise bits is null; num_bits should be zero. + else emp_assert(num_bits == 0); + + return true; + } + + + + // -------------------- Implementations of common accessors ------------------- + + /// Retrive the bit value from the specified index. + bool BitVector::Get(size_t index) const { + emp_assert(index < num_bits, index, num_bits); + const size_t field_id = FieldID(index); + const size_t pos_id = FieldPos(index); + return (bits[field_id] & (static_cast(1) << pos_id)) != 0; + } + + /// Update the bit value at the specified index. + BitVector & BitVector::Set(size_t index, bool value) { + emp_assert(index < num_bits, index, num_bits); + const size_t field_id = FieldID(index); + const size_t pos_id = FieldPos(index); + const field_t pos_mask = FIELD_1 << pos_id; + + if (value) bits[field_id] |= pos_mask; + else bits[field_id] &= ~pos_mask; + + return *this; + } + + /// Set all bits to 1. + BitVector & BitVector::SetAll() { + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = FIELD_ALL; + ClearExcessBits(); + return *this; + } + + /// Set all bits to 0. + BitVector & BitVector::Clear() { + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = FIELD_0; + return *this; + } + + /// Change a specified bit to the opposite value + BitVector & BitVector::Toggle(size_t index) { + emp_assert(index < num_bits, index, num_bits); + const size_t field_id = FieldID(index); + const size_t pos_id = FieldPos(index); + const field_t pos_mask = FIELD_1 << pos_id; + + bits[field_id] ^= pos_mask; + + return *this; + } + + bool BitVector::Any() const { + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; i++) { + if (bits[i]) return true; + } + return false; + } + + /// Resize this BitVector to have the specified number of bits. + BitVector & BitVector::Resize(size_t new_bits) { + const size_t old_num_fields = NumFields(); + num_bits = new_bits; + const size_t NUM_FIELDS = NumFields(); + + if (NUM_FIELDS == old_num_fields) { // We can use our existing bit field + num_bits = new_bits; + } + + else { // We must change the number of bitfields. Resize & copy old info. + Ptr old_bits = bits; // Backup old ptr. + if (num_bits > 0) bits = NewArrayPtr(NUM_FIELDS); // Allocate new mem. + else bits = nullptr; // (or null if no bits) + const size_t min_fields = std::min(old_num_fields, NUM_FIELDS); // Calc num fields to copy + for (size_t i = 0; i < min_fields; i++) bits[i] = old_bits[i]; // Copy fields + for (size_t i = min_fields; i < NUM_FIELDS; i++) bits[i] = FIELD_0; // Zero any excess fields + if (old_bits) old_bits.DeleteArray(); // Cleanup old memory + } + + ClearExcessBits(); // If there are ones past the end, zero them out. + + return *this; + } + + // ------------------------- Implementations Randomization functions ------------------------- + + /// Set all bits randomly, with a 50% probability of being a 0 or 1. + BitVector & BitVector::Randomize(Random & random) { + random.RandFill(BytePtr(), NumBytes()); + ClearExcessBits(); + return *this; + } + + /// Set all bits randomly, with probability specified at compile time. + template + BitVector & BitVector::RandomizeP(Random & random, + const size_t start_pos, size_t stop_pos) { + if (stop_pos == MAX_BITS) stop_pos = num_bits; + + emp_assert(start_pos <= stop_pos); + emp_assert(stop_pos <= num_bits); + random.RandFillP

(BytePtr(), NumBytes(), start_pos, stop_pos); + return *this; + } + + + /// Set all bits randomly, with a given probability of being on. + BitVector & BitVector::Randomize(Random & random, const double p, + const size_t start_pos, size_t stop_pos) { + if (stop_pos == MAX_BITS) stop_pos = num_bits; + + emp_assert(start_pos <= stop_pos, start_pos, stop_pos); + emp_assert(stop_pos <= num_bits, stop_pos, num_bits); + emp_assert(p >= 0.0 && p <= 1.0, p); + random.RandFill(BytePtr(), NumBytes(), p, start_pos, stop_pos); + return *this; + } + + /// Set all bits randomly, with a given number of them being on. + BitVector & BitVector::ChooseRandom(Random & random, const int target_ones, + const size_t start_pos, size_t stop_pos) { + if (stop_pos == MAX_BITS) stop_pos = num_bits; + + emp_assert(start_pos <= stop_pos); + emp_assert(stop_pos <= num_bits); + + const size_t target_size = stop_pos - start_pos; + emp_assert(target_ones >= 0); + emp_assert(target_ones <= (int) target_size); + + // Approximate the probability of ones as a starting point. + double p = ((double) target_ones) / (double) target_size; + + // If we are not randomizing the whole sequence, we need to track the number of ones + // in the NON-randomized region to subtract off later. + size_t kept_ones = 0; + if (target_size != num_bits) { + Clear(start_pos, stop_pos); + kept_ones = CountOnes(); + } + + // Try to find a shortcut if p allows.... + // (These values are currently educated guesses) + if (p < 0.12) { if (target_size == num_bits) Clear(start_pos, stop_pos); } + else if (p < 0.2) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.35) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.42) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.58) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.65) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.8) RandomizeP(random, start_pos, stop_pos); + else if (p < 0.88) RandomizeP(random, start_pos, stop_pos); + else SetRange(start_pos, stop_pos); + + size_t cur_ones = CountOnes() - kept_ones; + + // Do we need to add more ones? + while (cur_ones < (size_t) target_ones) { + size_t pos = random.GetUInt(start_pos, stop_pos); + auto bit = operator[](pos); + if (!bit) { + bit.Set(); + cur_ones++; + } + } + + // See if we have too many ones. + while (cur_ones > (size_t) target_ones) { + size_t pos = random.GetUInt(start_pos, stop_pos); + auto bit = operator[](pos); + if (bit) { + bit.Clear(); + cur_ones--; + } + } + + return *this; + } + + /// Flip random bits with a given probability. + // @CAO: Possibly faster to generate a sequence of bits and XORing with them. + BitVector & BitVector::FlipRandom(Random & random, + const double p, + const size_t start_pos, + size_t stop_pos) + { + if (stop_pos == MAX_BITS) stop_pos = num_bits; + + emp_assert(start_pos <= stop_pos); + emp_assert(stop_pos <= num_bits); + emp_assert(p >= 0.0 && p <= 1.0, p); + + for (size_t i=start_pos; i < stop_pos; ++i) if (random.P(p)) Toggle(i); + + return *this; + } + + /// Set random bits with a given probability (does not check if already set.) + BitVector & BitVector::SetRandom(Random & random, + const double p, + const size_t start_pos, + size_t stop_pos) + { + if (stop_pos == MAX_BITS) stop_pos = num_bits; + + emp_assert(start_pos <= stop_pos); + emp_assert(stop_pos <= num_bits); + emp_assert(p >= 0.0 && p <= 1.0, p); + + for (size_t i=start_pos; i < stop_pos; ++i) if (random.P(p)) Set(i); + + return *this; + } + + /// Unset random bits with a given probability (does not check if already zero.) + BitVector & BitVector::ClearRandom(Random & random, + const double p, + const size_t start_pos, + size_t stop_pos) + { + if (stop_pos == MAX_BITS) stop_pos = num_bits; + + emp_assert(start_pos <= stop_pos); + emp_assert(stop_pos <= num_bits); + emp_assert(p >= 0.0 && p <= 1.0, p); + + for (size_t i=start_pos; i < stop_pos; ++i) if (random.P(p)) Clear(i); + + return *this; + } + + /// Flip a specified number of random bits. + BitVector & BitVector::FlipRandomCount(Random & random, const size_t target_bits) + { + emp_assert(num_bits <= num_bits); + BitVector choice(num_bits, random, target_bits); + return XOR_SELF(choice); + } + + /// Set a specified number of random bits (does not check if already set.) + BitVector & BitVector::SetRandomCount(Random & random, const size_t target_bits) + { + emp_assert(num_bits <= num_bits); + BitVector choice(num_bits, random, target_bits); + return OR_SELF(choice); + } + + /// Unset a specified number of random bits (does not check if already zero.) + BitVector & BitVector::ClearRandomCount(Random & random, const size_t target_bits) + { + emp_assert(num_bits <= num_bits); + BitVector choice(num_bits, random, num_bits - target_bits); + return AND_SELF(choice); + } + + + // ------------------------- Implementations of Comparison Operators ------------------------- + + /// Test if two bit vectors are identical. + bool BitVector::operator==(const BitVector & in) const { + if (num_bits != in.num_bits) return false; + + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; ++i) { + if (bits[i] != in.bits[i]) return false; + } + return true; + } + + /// Compare the would-be numerical values of two bit vectors. + bool BitVector::operator<(const BitVector & in) const { + if (num_bits != in.num_bits) return num_bits < in.num_bits; + + const size_t NUM_FIELDS = NumFields(); + for (size_t i = NUM_FIELDS; i > 0; --i) { // Start loop at the largest field. + const size_t pos = i-1; + if (bits[pos] == in.bits[pos]) continue; // If same, keep looking! + return (bits[pos] < in.bits[pos]); // Otherwise, do comparison + } + return false; // Bit vectors are identical. + } + + /// Automatically convert BitVector to other vector types. + template + BitVector::operator emp::vector() { + emp::vector out(GetSize()); + for (size_t i = 0; i < GetSize(); i++) { + out[i] = (T) Get(i); + } + return out; + } + + + // ------------------------- Access Groups of bits ------------------------- + + /// Retrive the byte at the specified byte index. + uint8_t BitVector::GetByte(size_t index) const { + emp_assert(index < NumBytes(), index, NumBytes()); + const size_t field_id = Byte2Field(index); + const size_t pos_id = Byte2FieldPos(index); + return (bits[field_id] >> pos_id) & 255U; + } + + /// Get a read-only view into the internal array used by BitVector. + /// @return Read-only span of BitVector's bytes. + std::span BitVector::GetBytes() const { + return std::span( + bits.ReinterpretCast(), + NumBytes() + ); + } + + /// Update the byte at the specified byte index. + void BitVector::SetByte(size_t index, uint8_t value) { + emp_assert(index < NumBytes(), index, NumBytes()); + const size_t field_id = Byte2Field(index); + const size_t pos_id = Byte2FieldPos(index); + const field_t val_uint = value; + bits[field_id] = (bits[field_id] & ~(FIELD_255 << pos_id)) | (val_uint << pos_id); + } + + /// Get the overall value of this BitSet, using a uint encoding, but including all bits + /// and returning the value as a double. + double BitVector::GetValue() const { + const int max_one = FindMaxOne(); + + // If there are no ones, this value must be 0. + if (max_one == -1) return 0.0; + + // If all ones are in the least-significant field, just return it. + if (max_one < 64) return (double) GetUInt64(0); + + // To grab the most significant field, figure out how much to shift it by. + const int shift_bits = max_one - 63; + double out_value = (double) (*this >> shift_bits).GetUInt64(0); + + out_value *= emp::Pow2(shift_bits); + + return out_value; + } + + /// Get specified type at a given index (in steps of that type size) + template + T BitVector::GetValueAtIndex(const size_t index) const { + // For the moment, must fit inside bounds; eventually should pad with zeros. + emp_assert((index + 1) * sizeof(T) <= TotalBytes()); + + T out_value; + std::memcpy( &out_value, BytePtr().Raw() + index * sizeof(T), sizeof(T) ); + return out_value; + } + + + /// Set specified type at a given index (in steps of that type size) + template + void BitVector::SetValueAtIndex(const size_t index, T in_value) { + // For the moment, must fit inside bounds; eventually should pad with zeros. + emp_assert((index + 1) * sizeof(T) <= TotalBytes()); + + std::memcpy( BytePtr().Raw() + index * sizeof(T), &in_value, sizeof(T) ); + + ClearExcessBits(); + } + + + /// Get the specified type starting from a given BIT position. + template + T BitVector::GetValueAtBit(const size_t index) const { + // For the moment, must fit inside bounds; eventually should pad with zeros. + emp_assert((index+7)/8 + sizeof(T) < TotalBytes()); + + BitVector out_bits(*this); + out_bits >>= index; + + return out_bits.template GetValueAtIndex(0); + } + + + /// Set the specified type starting from a given BIT position. + // @CAO: Can be optimized substantially, especially for long BitVectors. + template + void BitVector::SetValueAtBit(const size_t index, T value) { + // For the moment, must fit inside bounds; eventually should (?) pad with zeros. + emp_assert((index+7)/8 + sizeof(T) < TotalBytes()); + constexpr size_t type_bits = sizeof(T) * 8; + + const size_t end_pos = Min(index+type_bits, num_bits); + Clear(index, end_pos); // Clear out the bits where new value will go. + BitVector in_bits(GetSize()); // Setup a bitset for the new bits. + in_bits.SetValueAtIndex(0, value); // Insert the new bits. + in_bits <<= index; // Shift new bits into place. + OR_SELF(in_bits); // Place new bits into current BitVector. + + ClearExcessBits(); + } + + + // ------------------------- Other Analyses ------------------------- + + /// A simple hash function for bit vectors. + std::size_t BitVector::Hash(size_t start_field) const { + static_assert(std::is_same_v, "Hash() requires fields to be size_t"); + + // If there are no fields left, hash on size one. + if (start_field == NumFields()) return num_bits; + + // If we have only one field left, combine it with size. + if (start_field == NumFields()-1) return hash_combine(bits[start_field], num_bits); + + // Otherwise we have more than one field. Combine and recurse. + size_t partial_hash = hash_combine(bits[start_field], bits[start_field+1]); + + return hash_combine(partial_hash, Hash(start_field+2)); + } + + // TODO: see https://arxiv.org/pdf/1611.07612.pdf for fast pop counts + /// Count the number of ones in the BitVector. + size_t BitVector::CountOnes() const { + if (num_bits == 0) return 0; + const field_t NUM_FIELDS = NumFields(); + size_t bit_count = 0; + for (size_t i = 0; i < NUM_FIELDS; i++) { + // when compiling with -O3 and -msse4.2, this is the fastest population count method. + std::bitset std_bs(bits[i]); + bit_count += std_bs.count(); + } + + return bit_count; + } + + /// Faster counting of ones for very sparse bit vectors. + size_t BitVector::CountOnes_Sparse() const { + size_t bit_count = 0; + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; ++i) { + field_t cur_field = bits[i]; + while (cur_field) { + cur_field &= (cur_field-1); // Peel off a single 1. + bit_count++; // Increment the counter + } + } + return bit_count; + } + + /// Pop the last bit in the vector. + /// @return value of the popped bit. + bool BitVector::PopBack() { + const bool val = Get(num_bits-1); + Resize(num_bits - 1); + return val; + } + + /// Push given bit(s) onto the back of a vector. + /// @param bit value of bit to be pushed. + /// @param num number of bits to be pushed. + void BitVector::PushBack(const bool bit, const size_t num) { + Resize(num_bits + num); + if (bit) SetRange(num_bits-num, num_bits); + } + + /// Insert bit(s) into any index of vector using bit magic. + /// Blog post on implementation reasoning: https://devolab.org/?p=2249 + /// @param index location to insert bit(s). + /// @param val value of bit(s) to insert (default true) + /// @param num number of bits to insert, default 1. + void BitVector::Insert(const size_t index, const bool val, const size_t num) { + Resize(num_bits + num); // Adjust to new number of bits. + BitVector low_bits(*this); // Copy current bits + SHIFT_SELF(-(int)num); // Shift the high bits into place. + Clear(0, index+num); // Reduce current to just high bits. + low_bits.Clear(index, num_bits); // Reduce copy to just low bits. + if (val) SetRange(index, index+num); // If new bits should be ones, make it so. + OR_SELF(low_bits); // Put the low bits back in place. + } + + + /// Delete bits from any index in a vector. + /// @param index location to delete bit(s). + /// @param num number of bits to delete, default 1. + void BitVector::Delete(const size_t index, const size_t num) { + emp_assert(index+num <= GetSize()); // Make sure bits to delete actually exist! + RawCopy(index+num, num_bits, index); // Shift positions AFTER delete into place. + Resize(num_bits - num); // Crop off end bits. + } + + /// Return the position of the first one; return -1 if no ones in vector. + int BitVector::FindOne() const { + const size_t NUM_FIELDS = NumFields(); + size_t field_id = 0; + while (field_id < NUM_FIELDS && bits[field_id]==0) field_id++; + return (field_id < NUM_FIELDS) ? + (int) (find_bit(bits[field_id]) + (field_id * FIELD_BITS)) : -1; + } + + /// Return the position of the first one after start_pos; return -1 if no ones in vector. + /// You can loop through all 1-bit positions of a BitVector "bv" with: + /// + /// for (int pos = bv.FindOne(); pos >= 0; pos = bv.FindOne(pos+1)) { ... } + + int BitVector::FindOne(const size_t start_pos) const { + if (start_pos >= num_bits) return -1; // If we're past the end, return fail. + size_t field_id = FieldID(start_pos); // What field do we start in? + const size_t field_pos = FieldPos(start_pos); // What position in that field? + + // If there's a hit in a partial first field, return it. + if (field_pos && (bits[field_id] & ~(MaskLow(field_pos)))) { + return (int) (find_bit(bits[field_id] & ~(MaskLow(field_pos))) + + field_id * FIELD_BITS); + } + + // Search other fields... + const size_t NUM_FIELDS = NumFields(); + if (field_pos) field_id++; + while (field_id < NUM_FIELDS && bits[field_id]==0) field_id++; + return (field_id < NUM_FIELDS) ? + (int) (find_bit(bits[field_id]) + (field_id * FIELD_BITS)) : -1; + } + + /// Find the most-significant set-bit. + int BitVector::FindMaxOne() const { + // Find the max field with a one. + int max_field = NumFields() - 1; + while (max_field >= 0 && bits[max_field] == 0) max_field--; + + // If there are no ones, return -1. + if (max_field == -1) return -1; + + const field_t field = bits[max_field]; // Save a local copy of this field. + field_t mask = (field_t) -1; // Mask off the bits still under consideration. + size_t offset = 0; // Indicate where the mask should be applied. + size_t range = FIELD_BITS; // Indicate how many bits are in the mask. + + while (range > 1) { + // Cut the range in half and see if we need to adjust the offset. + range /= 2; // Cut range size in half + mask >>= range; // Cut the mask down. + + // Check the upper half of original range; if has a one shift new offset to there. + if (field & (mask << (offset + range))) offset += range; + } + + return (int) (max_field * FIELD_BITS + offset); + } + + /// Return the position of the first one and change it to a zero. Return -1 if no ones. + int BitVector::PopOne() { + const int out_bit = FindOne(); + if (out_bit >= 0) Clear((size_t) out_bit); + return out_bit; + } + + /// Return positions of all ones. + emp::vector BitVector::GetOnes() const { + // @CAO -- There are better ways to do this with bit tricks. + emp::vector out_vals(CountOnes()); + size_t cur_pos = 0; + for (size_t i = 0; i < num_bits; i++) { + if (Get(i)) out_vals[cur_pos++] = i; + } + return out_vals; + } + + /// Find the length of the longest continuous series of ones. + size_t BitVector::LongestSegmentOnes() const { + size_t length = 0; + BitVector test_bits(*this); + while(test_bits.Any()){ + ++length; + test_bits.AND_SELF(test_bits<<1); + } + return length; + } + + /// Return true if any ones are in common with another BitVector. + bool BitVector::HasOverlap(const BitVector & in) const { + const size_t num_fields = std::min(NumFields(), in.NumFields()); + for (size_t i = 0; i < num_fields; ++i) { + // Short-circuit if we find any overlap. + if (bits[i] & in.bits[i]) return true; + } + return false; + } + + + // ------------------------- Printing and string conversion ------------------------- + + /// Convert this BitVector to a vector string [0 index on left] + std::string BitVector::ToString() const { + std::string out_string; + out_string.reserve(num_bits); + for (size_t i = 0; i < num_bits; ++i) out_string.push_back(GetAsChar(i)); + return out_string; + } + + /// Convert this BitVector to a numerical string [0 index on right] + std::string BitVector::ToBinaryString() const { + std::string out_string; + out_string.reserve(num_bits); + for (size_t i = num_bits; i > 0; --i) out_string.push_back(GetAsChar(i-1)); + return out_string; + } + + /// Convert this BitVector to a series of IDs + std::string BitVector::ToIDString(const std::string & spacer) const { + std::stringstream ss; + PrintOneIDs(ss, spacer); + return ss.str(); + } + + /// Convert this BitVector to a series of IDs with ranges condensed. + std::string BitVector::ToRangeString(const std::string & spacer, + const std::string & ranger) const + { + std::stringstream ss; + PrintAsRange(ss, spacer, ranger); + return ss.str(); + } + + /// Print a space between each field (or other provided spacer) + void BitVector::PrintFields(std::ostream & out, const std::string & spacer) const { + for (size_t i = num_bits-1; i < num_bits; i--) { + out << Get(i); + if (i && (i % FIELD_BITS == 0)) out << spacer; + } + } + + /// Print a space between each field (or other provided spacer) + void BitVector::PrintDebug(std::ostream & out) const { + for (size_t field = 0; field < NumFields(); field++) { + for (size_t bit_id = 0; bit_id < FIELD_BITS; bit_id++) { + bool bit = (FIELD_1 << bit_id) & bits[field]; + out << ( bit ? 1 : 0 ); + } + out << " : " << field << std::endl; + } + size_t end_pos = NumEndBits(); + if (end_pos == 0) end_pos = FIELD_BITS; + for (size_t i = 0; i < end_pos; i++) out << " "; + out << "^" << std::endl; + } + + /// Print the positions of all one bits, spaces are the default separator. + void BitVector::PrintOneIDs(std::ostream & out, const std::string & spacer) const { + bool started = false; + for (size_t i = 0; i < num_bits; i++) { + if (Get(i)) { + if (started) out << spacer; + out << i; + started = true; + } + } + } + + /// Print the ones in a range format. E.g., 2-5,7,10-15 + void BitVector::PrintAsRange(std::ostream & out, + const std::string & spacer, + const std::string & ranger) const + { + emp::vector ones = GetOnes(); + + for (size_t pos = 0; pos < ones.size(); pos++) { + if (pos) out << spacer; + + size_t start = ones[pos]; + while (pos+1 < ones.size() && ones[pos+1] == ones[pos]+1) pos++; + size_t end = ones[pos]; + + out << start; + if (start != end) out << ranger << end; + } + } + + + // ------------------------- Base Boolean-logic operations ------------------------- + + /// Perform a Boolean NOT with this BitVector, store result here, and return this object. + BitVector & BitVector::NOT_SELF() { + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = ~bits[i]; + ClearExcessBits(); + return *this; + } + + /// Perform a Boolean AND with this BitVector, store result here, and return this object. + BitVector & BitVector::AND_SELF(const BitVector & bv2) { + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = bits[i] & bv2.bits[i]; + return *this; + } + + /// Perform a Boolean OR with this BitVector, store result here, and return this object. + BitVector & BitVector::OR_SELF(const BitVector & bv2) { + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = bits[i] | bv2.bits[i]; + return *this; + } + + /// Perform a Boolean NAND with this BitVector, store result here, and return this object. + BitVector & BitVector::NAND_SELF(const BitVector & bv2) { + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = ~(bits[i] & bv2.bits[i]); + ClearExcessBits(); + return *this; + } + + /// Perform a Boolean NOR with this BitVector, store result here, and return this object. + BitVector & BitVector::NOR_SELF(const BitVector & bv2) { + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = ~(bits[i] | bv2.bits[i]); + ClearExcessBits(); + return *this; + } + + /// Perform a Boolean XOR with this BitVector, store result here, and return this object. + BitVector & BitVector::XOR_SELF(const BitVector & bv2) { + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = bits[i] ^ bv2.bits[i]; + return *this; + } + + /// Perform a Boolean EQU with this BitVector, store result here, and return this object. + BitVector & BitVector::EQU_SELF(const BitVector & bv2) { + const size_t NUM_FIELDS = NumFields(); + for (size_t i = 0; i < NUM_FIELDS; i++) bits[i] = ~(bits[i] ^ bv2.bits[i]); + ClearExcessBits(); + return *this; + } + + /// Positive shifts go left and negative go right (0 does nothing); return result. + BitVector BitVector::SHIFT(const int shift_size) const { + BitVector out_bv(*this); + if (shift_size > 0) out_bv.ShiftRight((size_t) shift_size); + else if (shift_size < 0) out_bv.ShiftLeft((size_t) -shift_size); + return out_bv; + } + + /// Positive shifts go left and negative go right; store result here, and return this object. + BitVector & BitVector::SHIFT_SELF(const int shift_size) { + if (shift_size > 0) ShiftRight((size_t) shift_size); + else if (shift_size < 0) ShiftLeft((size_t) -shift_size); + return *this; + } + + /// Reverse the order of bits in the bitset + BitVector & BitVector::REVERSE_SELF() { + // reverse bytes + std::reverse( BytePtr().Raw(), BytePtr().Raw() + NumBytes() ); + + // reverse each byte + // adapted from https://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte + for (size_t i = 0; i < NumBytes(); ++i) { + unsigned char & b = BytePtr()[i]; + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + } + + // shift out filler bits + size_t filler_bits = num_bits % 8; + if (filler_bits) { + this->ShiftRight(8-filler_bits); + } + + return *this; + + } + + /// Reverse order of bits in the bitset. + BitVector BitVector::REVERSE() const { + BitVector out_set(*this); + return out_set.REVERSE_SELF(); + } + + + /// Positive rotates go left and negative rotates go left (0 does nothing); + /// return result. + BitVector BitVector::ROTATE(const int rotate_size) const { + BitVector out_set(*this); + if (rotate_size > 0) out_set.RotateRight((field_t) rotate_size); + else if (rotate_size < 0) out_set.RotateLeft((field_t) (-rotate_size)); + return out_set; + } + + /// Positive rotates go right and negative rotates go left (0 does nothing); + /// store result here, and return this object. + BitVector & BitVector::ROTATE_SELF(const int rotate_size) { + if (rotate_size > 0) RotateRight((field_t) rotate_size); + else if (rotate_size < 0) RotateLeft((field_t) -rotate_size); + return *this; + } + + /// Helper: call ROTATE with negative number instead + template + BitVector & BitVector::ROTL_SELF() { + constexpr size_t shift_size = shift_size_raw % num_bits; + const size_t NUM_FIELDS = NumFields(); + const size_t LAST_FIELD = LastField(); + + // special case: for exactly one field_t, try to go low level + // adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c + if (NUM_FIELDS == 1) { + field_t & n = bits[0]; + size_t c = shift_size; + + // mask necessary to suprress shift count overflow warnings + c &= FIELD_LOG2_MASK; + n = (n<>( (-(c+FIELD_BITS-num_bits)) & FIELD_LOG2_MASK )); + + } else { + + // note that we already modded shift_size by num_bits + // so there's no need to mod by FIELD_SIZE here + int field_shift = NumEndBits() ? ( + (shift_size + FIELD_BITS - NumEndBits()) / FIELD_BITS + ) : ( + shift_size / FIELD_BITS + ); + // if we field shift, we need to shift bits by (FIELD_BITS - NumEndBits()) + // more to account for the filler that gets pulled out of the middle + int bit_shift = NumEndBits() && field_shift ? ( + (shift_size + FIELD_BITS - NumEndBits()) % FIELD_BITS + ) : ( + shift_size % FIELD_BITS + ); + int bit_overflow = FIELD_BITS - bit_shift; + + // if rotating more than field capacity, we need to rotate fields + if (field_shift) { + auto field_span = FieldSpan(); + std::rotate( + field_span.rbegin(), + field_span.rbegin()+field_shift, + field_span.rend() + ); + } + + // if necessary, shift filler bits out of the middle + if (NumEndBits()) { + const int filler_idx = (LAST_FIELD + field_shift) % NUM_FIELDS; + for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { + bits[i-1] |= bits[i] << NumEndBits(); + bits[i] >>= (FIELD_BITS - NumEndBits()); + } + } + + // account for bit_shift + if (bit_shift) { + + const field_t keystone = NumEndBits() ? ( + (bits[LAST_FIELD] << (FIELD_BITS - NumEndBits())) + | (bits[NUM_FIELDS - 2] >> NumEndBits()) + ) : ( + bits[LAST_FIELD] + ); + + for (int i = LAST_FIELD; i > 0; --i) { + bits[i] <<= bit_shift; + bits[i] |= (bits[i-1] >> bit_overflow); + } + // Handle final field + bits[0] <<= bit_shift; + bits[0] |= keystone >> bit_overflow; + + } + + } + + ClearExcessBits(); + + return *this; + + } + + + /// Helper for calling ROTATE with positive number + template + BitVector & BitVector::ROTR_SELF() { + const size_t shift_size = shift_size_raw % num_bits; + const size_t NUM_FIELDS = NumFields(); + const size_t LAST_FIELD = LastField(); + + // special case: for exactly one field_t, try to go low level + // adapted from https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c + if (NUM_FIELDS == 1) { + field_t & n = bits[0]; + size_t c = shift_size; + + // mask necessary to suprress shift count overflow warnings + c &= FIELD_LOG2_MASK; + n = (n>>c) | (n<<( (num_bits-c) & FIELD_LOG2_MASK )); + + } else { + + field_t field_shift = (shift_size / FIELD_BITS) % NUM_FIELDS; + int bit_shift = shift_size % FIELD_BITS; + field_t bit_overflow = FIELD_BITS - bit_shift; + + // if rotating more than field capacity, we need to rotate fields + if (field_shift) { + auto field_span = FieldSpan(); + std::rotate( + field_span.begin(), + field_span.begin()+field_shift, + field_span.end() + ); + } + + // if necessary, shift filler bits out of the middle + if (NumEndBits()) { + int filler_idx = LAST_FIELD - field_shift; + for (int i = filler_idx + 1; i < (int)NUM_FIELDS; ++i) { + bits[i-1] |= bits[i] << NumEndBits(); + bits[i] >>= (FIELD_BITS - NumEndBits()); + } + } + + // account for bit_shift + if (bit_shift) { + + const field_t keystone = NumEndBits() ? ( + bits[0] >> (FIELD_BITS - NumEndBits()) + ) : ( + bits[0] + ); + + if (NumEndBits()) { + bits[LastField()] |= bits[0] << NumEndBits(); + } + + for (size_t i = 0; i < LAST_FIELD; ++i) { + bits[i] >>= bit_shift; + bits[i] |= (bits[i+1] << bit_overflow); + } + bits[LAST_FIELD] >>= bit_shift; + bits[LAST_FIELD] |= keystone << bit_overflow; + } + } + + ClearExcessBits(); + + return *this; + + } + + /// Addition of two Bitsets. + /// Wraps if it overflows. + /// Returns result. + BitVector BitVector::ADD(const BitVector & set2) const{ + BitVector out_set(*this); + return out_set.ADD_SELF(set2); + } + + /// Addition of two Bitsets. + /// Wraps if it overflows. + /// Returns this object. + BitVector & BitVector::ADD_SELF(const BitVector & set2) { + bool carry = false; + + for (size_t i = 0; i < num_bits/FIELD_BITS; ++i) { + field_t addend = set2.bits[i] + static_cast(carry); + carry = set2.bits[i] > addend; + + field_t sum = bits[i] + addend; + carry |= bits[i] > sum; + + bits[i] = sum; + } + + if (NumEndBits()) { + bits[num_bits/FIELD_BITS] = ( + bits[num_bits/FIELD_BITS] + + set2.bits[num_bits/FIELD_BITS] + + static_cast(carry) + ) & EndMask(); + } + + return *this; + } + + /// Subtraction of two Bitsets. + /// Wraps around if it underflows. + /// Returns result. + BitVector BitVector::SUB(const BitVector & set2) const{ + BitVector out_set(*this); + return out_set.SUB_SELF(set2); + } + + /// Subtraction of two Bitsets. + /// Wraps if it underflows. + /// Returns this object. + BitVector & BitVector::SUB_SELF(const BitVector & set2){ + + bool carry = false; + + for (size_t i = 0; i < num_bits/FIELD_BITS; ++i) { + field_t subtrahend = set2.bits[i] + static_cast(carry); + carry = set2.bits[i] > subtrahend; + carry |= bits[i] < subtrahend; + bits[i] -= subtrahend; + } + + if (NumEndBits()) { + bits[num_bits/FIELD_BITS] = ( + bits[num_bits/FIELD_BITS] + - set2.bits[num_bits/FIELD_BITS] + - static_cast(carry) + ) & EndMask(); + } + + return *this; + } + +} + + +// ---------------------- Implementations to work with standard library ---------------------- namespace std { /// Hash function to allow BitVector to be used with maps and sets (must be in std). template <> struct hash { - std::size_t operator()(const emp::BitVector & b) const { - return b.Hash(); + std::size_t operator()(const emp::BitVector & bv) const { + return bv.Hash(); } }; - - /// operator<< to work with ostream (must be in std to work) - inline std::ostream & operator<<(std::ostream & out, const emp::BitVector & bit_v) { - bit_v.Print(out); - return out; - } } #endif diff --git a/include/emp/bits/_bitset_helpers.hpp b/include/emp/bits/_bitset_helpers.hpp new file mode 100644 index 0000000000..06e1babcbb --- /dev/null +++ b/include/emp/bits/_bitset_helpers.hpp @@ -0,0 +1,60 @@ +/** + * @note This file is part of Empirical, https://github.com/devosoft/Empirical + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2020. + * + * @file _bitset_helpers.hpp + * @brief An internal Empirical class with tools to build collections of bits. + */ + + +#ifndef EMP_BIT_SET_HELPERS_H +#define EMP_BIT_SET_HELPERS_H + +namespace emp { + + /// BitProxy lets us use operator[] on with BitVector or BitSet as an lvalue. + template + class BitProxy { + private: + T & bit_container; ///< Which container does this proxy belong to? + size_t index; ///< Which position in the bit vector does this proxy point at? + + public: + // Helper functions. + inline bool Get() const { return bit_container.Get(index); } + inline BitProxy & Set(bool b=true) { bit_container.Set(index, b); return *this; } + inline BitProxy & Clear() { bit_container.Clear(index); return *this; } + inline BitProxy & Toggle() { bit_container.Toggle(index); return *this; } + inline BitProxy & SetIf(bool test, bool b) { if (test) Set(b); return *this; } + inline BitProxy & ToggleIf(bool test) { if (test) Toggle(); return *this; } + + /// Setup a new proxy with the associated vector and index. + BitProxy(T & _v, size_t _idx) : bit_container(_v), index(_idx) {;} + + /// Assignment operator to the bit associated with this proxy (as an lvalue). + BitProxy & operator=(bool b) { return Set(b); } + + /// Conversion of this proxy to Boolean (as an rvalue) + operator bool() const { return Get(); } + + // Compound assignement operators with BitProxy as the lvalue. + BitProxy & operator &=(bool b) { return SetIf(!b, 0); } + BitProxy & operator *=(bool b) { return SetIf(!b, 0); } + BitProxy & operator |=(bool b) { return SetIf(b, 1); } + BitProxy & operator +=(bool b) { return SetIf(b, 1); } + BitProxy & operator -=(bool b) { return SetIf(b, 0); } + + /// Compound assignement operator XOR using BitProxy as lvalue. + BitProxy & operator ^=(bool b) { return ToggleIf(b); } + + /// Compound assignement operator DIV using BitProxy as lvalue. + /// @note Never use this function except for consistency in a template since must divide by 1. + BitProxy & operator /=([[maybe_unused]] bool b) { + emp_assert(b == true, "BitProxy Division by Zero error."); + return *this; + } + }; // --- End of BitProxy +} + +#endif \ No newline at end of file diff --git a/include/emp/compiler/DFA.hpp b/include/emp/compiler/DFA.hpp index e4530ee99b..9bae738869 100644 --- a/include/emp/compiler/DFA.hpp +++ b/include/emp/compiler/DFA.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2016-2017 + * @date 2016-2021. * * @file DFA.hpp * @brief A Deterministic Finite Automata simulator. @@ -83,7 +83,7 @@ namespace emp { // If a size_t is passed in, it can't be -1... stop_t GetStop(size_t state) const { return is_stop[state]; } - bool IsActive(size_t state) const { return true; } + bool IsActive(size_t /* state */) const { return true; } bool IsStop(size_t state) const { return is_stop[state]; } /// Return the new state after a symbol occurs. diff --git a/include/emp/compiler/Lexer.hpp b/include/emp/compiler/Lexer.hpp index bb10df9444..a7c7389df9 100644 --- a/include/emp/compiler/Lexer.hpp +++ b/include/emp/compiler/Lexer.hpp @@ -228,6 +228,16 @@ namespace emp { return Tokenize(ss); } + /// Turn a vector of strings into a vector of tokens. + emp::vector Tokenize(const emp::vector & str_v) { + std::stringstream ss; + for (auto & str : str_v) { + ss << str; + } + + return Tokenize(ss); + } + /// Get the lexeme associated with the last token identified. const std::string & GetLexeme() { return lexeme; } diff --git a/include/emp/compiler/NFA.hpp b/include/emp/compiler/NFA.hpp index 400467b720..8039a0a406 100644 --- a/include/emp/compiler/NFA.hpp +++ b/include/emp/compiler/NFA.hpp @@ -128,16 +128,26 @@ namespace emp { size_t AddNewState() { size_t new_state = GetSize(); Resize(new_state+1); return new_state; } /// Add a transition between states 'from' and 'to' that can be taken with the provided symbol. - void AddTransition(size_t from, size_t to, size_t sym) { + void AddTransitionSymbol(size_t from, size_t to, size_t sym) { emp_assert(from < states.size(), from, states.size()); emp_assert(to < states.size(), to, states.size()); states[from].trans[to].symbols[sym] = true; } + /// Add a transition between states 'from' and 'to' that can be taken with a char symbol. + void AddTransition(size_t from, size_t to, const char sym) { + AddTransitionSymbol(from, to, sym); + } + /// Add a transition between states 'from' and 'to' that can be taken with the provided symbols. void AddTransition(size_t from, size_t to, const std::string & sym_set) { - for (char x : sym_set) AddTransition(from, to, (size_t) x); + for (char x : sym_set) AddTransitionSymbol(from, to, (size_t) x); + } + + /// Add a transition between states 'from' and 'to' that can be taken with the provided symbols. + void AddTransition(size_t from, size_t to, const char * sym_set) { + AddTransition(from, to, std::string(sym_set)); } /// Add a transition between states 'from' and 'to' that can be taken with the provided symbols. diff --git a/include/emp/compiler/RegEx.hpp b/include/emp/compiler/RegEx.hpp index 27fe7d3e75..3b6438c131 100644 --- a/include/emp/compiler/RegEx.hpp +++ b/include/emp/compiler/RegEx.hpp @@ -131,7 +131,7 @@ namespace emp { } Ptr AsCharSet() override { return ToPtr(this); } size_t GetSize() const override { return char_set.CountOnes(); } - char First() const { return (char) char_set.FindBit(); } + char First() const { return (char) char_set.FindOne(); } virtual void AddToNFA(NFA & nfa, size_t start, size_t stop) const override { for (size_t i = 0; i < NUM_SYMBOLS; i++) if (char_set[i]) nfa.AddTransition(start, stop, i); } diff --git a/include/emp/config/ArgManager.hpp b/include/emp/config/ArgManager.hpp index 1ab9032495..7644b514c1 100644 --- a/include/emp/config/ArgManager.hpp +++ b/include/emp/config/ArgManager.hpp @@ -356,7 +356,7 @@ namespace emp { 1, "Command name.", {}, - [](emp::optional> res){ /*no-op*/ } + [](emp::optional> /* res */ ){ /*no-op*/ } )}, {"help", ArgSpec(0, "Print help information.", {"h"})}, {"gen", ArgSpec( diff --git a/include/emp/config/command_line.hpp b/include/emp/config/command_line.hpp index 6693cee9ca..5da954b74a 100644 --- a/include/emp/config/command_line.hpp +++ b/include/emp/config/command_line.hpp @@ -50,6 +50,13 @@ namespace emp { return args; } + // Use an argument in a given position OR a default value of that arg doesn't exist. + template + T read_arg_pos(const emp::vector & args, size_t pos, T default_val=T()) { + if (args.size() <= pos) return default_val; + return emp::from_string(args[pos]); + } + // Search through args to find a specific value. int find_arg(const emp::vector & args, const std::string & pattern) { for (size_t i = 0; i < args.size(); i++) { @@ -103,6 +110,8 @@ namespace emp { } + + using namespace cl; } #endif diff --git a/include/emp/config/config.hpp b/include/emp/config/config.hpp index e694aa288d..fbe68d49b7 100644 --- a/include/emp/config/config.hpp +++ b/include/emp/config/config.hpp @@ -48,6 +48,7 @@ #include "../base/unordered_map.hpp" #include "../base/vector.hpp" #include "../datastructs/map_utils.hpp" +#include "../meta/macros.hpp" #include "../tools/string_utils.hpp" #include "ConfigManager.hpp" diff --git a/include/emp/data/DataFile.hpp b/include/emp/data/DataFile.hpp index d6f04eb80e..7739dd6011 100644 --- a/include/emp/data/DataFile.hpp +++ b/include/emp/data/DataFile.hpp @@ -663,8 +663,12 @@ namespace emp { /// Add a function that returns a value to be printed to the file. template - size_t AddContainerFun(const std::function & fun, const std::string & key="", const std::string & desc="") { - std::function in_fun = [fun](std::ostream & os, const data_t data){ os << fun(data); }; + size_t AddContainerFun(const std::function & fun, + const std::string & key="", + const std::string & desc="") + { + std::function in_fun = + [fun](std::ostream & os, const data_t data){ os << fun(data); }; return Add(in_fun, key, desc); } @@ -679,10 +683,10 @@ namespace emp { /// @param e character to print at the end of each line template ContainerDataFile MakeContainerDataFile(std::function fun, - const std::string & filename, - const std::string & b="", - const std::string & s=",", - const std::string & e="\n") { + const std::string & filename, + const std::string & b="", + const std::string & s=",", + const std::string & e="\n") { ContainerDataFile dfile(filename, b, s, e); dfile.SetUpdateContainerFun(fun); return dfile; diff --git a/include/emp/data/DataMap.hpp b/include/emp/data/DataMap.hpp index 6b8dbd63e2..1abb9f94b4 100644 --- a/include/emp/data/DataMap.hpp +++ b/include/emp/data/DataMap.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2018-2020. + * @date 2018-2021. * * @file DataMap.hpp * @brief A DataMap links names to arbitrary object types. @@ -100,8 +100,8 @@ namespace emp { class DataMap { protected: - MemoryImage memory; ///< Memory status for this Map. - emp::Ptr layout_ptr; ///< Which layout are we using? + MemoryImage memory; ///< Memory contents for this Map. + emp::Ptr layout_ptr; ///< Layout we are using (shared across maps w/ same format) DataMap(emp::Ptr in_layout_ptr, size_t in_size) : memory(in_size), layout_ptr(in_layout_ptr) { ; } @@ -290,12 +290,20 @@ namespace emp { return layout_ptr->Add(memory, name, default_value, desc, notes); } + /// Test if this DataMap uses the specified layout. + bool HasLayout(const emp::DataLayout & in_layout) const { + return layout_ptr == &in_layout; + } + /// Test if this DataMap is using the identical layout as another DataMap. bool SameLayout(const emp::DataMap & in_dm) const { return layout_ptr == in_dm.layout_ptr; // @CAO: Should we also see if it's using a different layout object, but otherwise identical? } + /// Get the DataLayout so that it can be used elsewhere. + const emp::DataLayout & GetLayout() { return *layout_ptr; } + /// Test if this layout is locked (i.e., it cannot be changed.) bool IsLocked() const { return layout_ptr && layout_ptr->IsLocked(); } diff --git a/include/emp/datastructs/StringMap.hpp b/include/emp/datastructs/StringMap.hpp index 0e0af9fead..efafba18e4 100644 --- a/include/emp/datastructs/StringMap.hpp +++ b/include/emp/datastructs/StringMap.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2018-2019. + * @date 2018-2021. * * @file StringMap.hpp * @brief An std::unordered_map wrapper that deals smoothly with strings and fast compile-time optimizations. @@ -26,21 +26,6 @@ #include "../base/unordered_map.hpp" #include "../tools/string_utils.hpp" -/// Macro to covert a literal string to a unique ID, mostly at compile time. Specifically, -/// the string is converted to a unique type at compile time, which is then mapped to a unique -/// function. That function is run a run-time, but preserves the id to return so it is -/// calculated only once. -#define EMP_STRING(STR) \ - ([](){ \ - constexpr auto temp = EMP_TEXT_PACK(STR); \ - return emp::StringID::Get(); \ - }()) - - -/// Macro to build a fake member function for StringMap that can only be passed a literal -/// string that will be converted at compile time. -#define CTGet(STR) Get( EMP_STRING(STR) ) - namespace emp { diff --git a/include/emp/datastructs/hash_utils.hpp b/include/emp/datastructs/hash_utils.hpp index 929700a5d4..abfcac10d5 100644 --- a/include/emp/datastructs/hash_utils.hpp +++ b/include/emp/datastructs/hash_utils.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020 + * @date 2019-2021. * * @file hash_utils.hpp * @brief This file provides tools for hashing values and containers. @@ -17,36 +17,99 @@ #include #include +#include "../base/Ptr.hpp" #include "../polyfill/span.hpp" namespace emp { + namespace internal { + // Allow a hash to be determined by a Hash() member function. + template + auto Hash_impl(const T & x, bool) noexcept -> decltype(x.Hash()) { return x.Hash(); } + + // By default, use std::hash if nothing else exists. + template + auto Hash_impl(const T & x, int) noexcept -> decltype(std::hash()(x)) { return std::hash()(x); } + + // Try direct cast to size_t if nothing else works. + template + std::size_t Hash_impl(const T & x, ...) noexcept { + // @CAO Setup directory structure to allow the following to work: + // LibraryWarning("Resorting to casting to size_t for emp::Hash implementation."); + return (size_t) x; + } + } + + // Setup hashes to be dynamically determined + template + std::size_t Hash(const T & x) noexcept { return internal::Hash_impl(x, true); } + + /// Generate a unique long from a pair of ints. /// @param a_ First 32-bit unsigned int. /// @param b_ Second 32-bit unsigned int. /// @return 64-bit unsigned int representing the szudzik hash of both inputs. - inline uint64_t szudzik_hash(uint32_t a_, uint32_t b_) + inline uint64_t szudzik_hash(uint32_t a_, uint32_t b_) noexcept { uint64_t a = a_, b = b_; return a >= b ? a * a + a + b : a + b * b; } + + /// If hash_combine has a single value, there's nothing to combine; just return it! + constexpr inline std::size_t hash_combine(std::size_t hash1) noexcept { return hash1; } + /// Boost's implementation of a simple hash-combining function. /// Taken from https://www.boost.org/doc/libs/1_37_0/doc/html/hash/reference.html#boost.hash_combine /// @param hash1 First hash to combine. /// @param hash2 Second hash to combine. /// @return Combined hash. - constexpr inline std::size_t hash_combine(std::size_t hash1, std::size_t hash2) + constexpr inline std::size_t hash_combine(std::size_t hash1, std::size_t hash2) noexcept { return hash1 ^ (hash2 + 0x9e3779b9 + (hash1 << 6) + (hash1 >> 2)); } + /// Allow hash_combine to work with more than two input values. + template + constexpr inline std::size_t hash_combine(std::size_t hash1, std::size_t hash2, + std::size_t hash3, Ts... extras) noexcept + { + // combine the first two, put them at the end (so the same ones don't keep getting recombined + // every step of the way through), and recurse. + std::size_t partial_hash = hash_combine(hash1, hash2); + return hash_combine(hash3, extras..., partial_hash); + } + + /// Allow hash_combine to take a series of size_t's to merge into a single hash. + inline std::size_t hash_combine(emp::Ptr hashes, size_t num_hashes) noexcept + { + emp_assert(num_hashes > 0); // At least one hash is required! + if (num_hashes == 1) return hashes[0]; // If we have exactly one, just return it. + + // Combine the last two hashes into partial hash. + const std::size_t partial_hash = hash_combine(hashes[num_hashes-1], hashes[num_hashes-2]); + if (num_hashes == 2) return partial_hash; + + // Recurse! + const std::size_t partial_hash2 = hash_combine(hashes, num_hashes-2); + + return hash_combine(partial_hash, partial_hash2); + } + + /// Alias hash_combine() to CombineHash() + template + // inline std::size_t CombineHash(Ts &&... args) { + // return hash_combine(Hash(std::forward(args))...); + inline std::size_t CombineHash(const Ts &... args) { + return hash_combine(Hash(args)...); + } + // helper functions for murmur hash #ifndef DOXYGEN_SHOULD_SKIP_THIS namespace internal { - constexpr inline uint64_t rotate(const size_t x, const size_t r) { + constexpr inline uint64_t rotate(const size_t x, const size_t r) noexcept { return (x << r) | (x >> (64 - r)); } - constexpr inline void fmix64(uint64_t& k) { + constexpr inline void fmix64(uint64_t& k) noexcept { k ^= k >> 33; k *= 0xff51afd7ed558ccd; k ^= k >> 33; @@ -67,7 +130,7 @@ namespace emp { constexpr inline size_t murmur_hash( const std::span key, const size_t seed = 0 - ) { + ) noexcept { // define constants const size_t numbytes = key.size(); const size_t nblocks = numbytes / 16; diff --git a/include/emp/datastructs/tuple_struct.hpp b/include/emp/datastructs/tuple_struct.hpp index 6facb0bd86..6baf17731a 100644 --- a/include/emp/datastructs/tuple_struct.hpp +++ b/include/emp/datastructs/tuple_struct.hpp @@ -1,5 +1,5 @@ // This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2015-2017. +// Copyright (C) Michigan State University, 2015-2021. // Released under the MIT Software license; see doc/LICENSE // @@ -69,7 +69,7 @@ #include #include -#include "../base/macros.hpp" +#include "../meta/macros.hpp" #include "../meta/meta.hpp" #ifndef DOXYGEN_SHOULD_SKIP_THIS diff --git a/include/emp/datastructs/tuple_utils.hpp b/include/emp/datastructs/tuple_utils.hpp index b8d31ac35e..b4a95cb34c 100644 --- a/include/emp/datastructs/tuple_utils.hpp +++ b/include/emp/datastructs/tuple_utils.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2016-2018 + * @date 2016-2021. * * @file tuple_utils.hpp * @brief Functions to simplify the use of std::tuple @@ -14,8 +14,8 @@ #include #include +#include "hash_utils.hpp" #include "../meta/ValPack.hpp" -#include "../meta/meta.hpp" namespace emp { @@ -31,21 +31,6 @@ namespace emp { } - /// Apply a tuple as arguments to a function, where all argument positions in function are - /// specified with and ValPack - template < typename FUN_T, typename TUPLE_T, int... N > // Specify positions to apply... - auto ApplyTuple(const FUN_T & fun, const TUPLE_T & tup, ValPack) { - return fun(std::get(tup)...); - } - - /// Apply a tuple as arguments to a function, in order. - template // Apply whole tuple - auto ApplyTuple(const FUN_T & fun, const TUPLE_T & tup) { - return ApplyTuple(fun, tup, ValPackRange<0,tuple_size()>()); - } - - - /// Setup tuples to be able to be used in hash tables. template struct TupleHash { @@ -53,7 +38,10 @@ namespace emp { using fun_t = std::function; std::size_t operator()( const tuple_t & tup ) const { - return ApplyTuple (emp::CombineHash, tup); + return std::apply ( + [](TYPES... args) { return emp::CombineHash(args...); }, + tup + ); } }; @@ -71,7 +59,7 @@ namespace emp { // End case... we've already hit all elements in the tuple! template struct TupleIterate_impl { - static void Run(TUPLE_T & tup, const FUN_T & fun) { ; } + static void Run(TUPLE_T & /* tup */, const FUN_T & /* fun */) { ; } }; } #endif // DOXYGEN_SHOULD_SKIP_THIS diff --git a/include/emp/datastructs/vector_utils.hpp b/include/emp/datastructs/vector_utils.hpp index 3e7cc87c29..5f736f79ca 100644 --- a/include/emp/datastructs/vector_utils.hpp +++ b/include/emp/datastructs/vector_utils.hpp @@ -1,14 +1,15 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2017-2020. + * @date 2017-2021. * * @file vector_utils.hpp * @brief A set of simple functions to manipulate emp::vector * @note Status: BETA * * - * @note consider adding a work-around to avoid vector ? + * @todo consider adding a work-around to avoid vector ? + * @todo speed up Append to count all additions at once, resize, and fill them in. */ #ifndef EMP_VECTOR_UTILS_H @@ -21,6 +22,7 @@ #include #include "../base/vector.hpp" +#include "../tools/string_utils.hpp" namespace emp { @@ -51,7 +53,7 @@ namespace emp { /// Concatonate two or more vectors together, creating a new vector. template emp::vector Concat(const emp::vector & v1, const Vs &... vs) { - emp::vector out_v = v1; + emp::vector out_v(v1); Append(out_v, vs...); return out_v; } @@ -91,7 +93,7 @@ namespace emp { void Print(const emp::vector & v, std::ostream & os=std::cout, const std::string & spacer=" ") { for (size_t id = 0; id < v.size(); id++) { if (id) os << spacer; // Put a space before second element and beyond. - os << v[id]; + os << emp::to_string(v[id]); } } @@ -145,6 +147,13 @@ namespace emp { template T FindMax(const emp::vector & v) { return v[ FindMaxIndex(v) ]; } + /// Find the intersection between this vector and another container. + template + emp::vector FindIntersect(const emp::vector & in1, const C2 & in2) { + emp::vector out; + for (const auto & x : in1) if (emp::Has(in2, x)) out.push_back(x); + return out; + } /// Sum all of the contents of a vector. template diff --git a/include/emp/debug/debug.hpp b/include/emp/debug/debug.hpp index d49dfb33b5..e34568f39f 100644 --- a/include/emp/debug/debug.hpp +++ b/include/emp/debug/debug.hpp @@ -20,10 +20,16 @@ namespace emp { - /// BlockRelease() will halt compilation if NDEBUG is on. It is useful to include alongside - /// debug print code that you want to remember to remove when you are done debugging. + /// BlockRelease() will halt compilation if NDEBUG is on and EMP_NO_BLOCK is off. + /// It is useful to include alongside debug code that you want to remember to remove when you + /// are done debugging; it is automatically included with the emp_debug() function below. + /// If you want to intentionally compile in release mode, make sure to define EMP_NO_BLOCK. #ifdef NDEBUG - #define BlockRelease(BLOCK) static_assert(!BLOCK, "Release blocked due to debug material.") + #ifdef EMP_NO_BLOCK + #define BlockRelease(BLOCK) + #else + #define BlockRelease(BLOCK) static_assert(!BLOCK, "Release blocked due to debug material.") + #endif #else #define BlockRelease(BLOCK) #endif @@ -35,6 +41,15 @@ namespace emp { #define EMP_DEBUG(...) __VA_ARGS__ #endif + template + void emp_debug_print(Ts &&... args) { + (std::cerr << ... << std::forward(args)); + std::cerr << std::endl; + } + + /// emp_debug() will print its contents as a message in debug mode and BLOCK release mode until it's removed. + #define emp_debug(...) BlockRelease(true); emp::emp_debug_print(__VA_ARGS__); + /// Depricated() prints its contents exactly once to notify a user of a depricated function. static void Depricated(const std::string & name, const std::string & desc="") { static std::set name_set; diff --git a/include/emp/games/PayoffMatrix.hpp b/include/emp/games/PayoffMatrix.hpp index 9bb6afa92c..5443e13838 100644 --- a/include/emp/games/PayoffMatrix.hpp +++ b/include/emp/games/PayoffMatrix.hpp @@ -1,5 +1,5 @@ // This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2016-2017. +// Copyright (C) Michigan State University, 2016-2021. // Released under the MIT Software license; see doc/LICENSE // // @@ -12,6 +12,7 @@ #include #include "../base/array.hpp" +#include "../base/vector.hpp" namespace emp { diff --git a/include/emp/hardware/AvidaCPU_InstLib.hpp b/include/emp/hardware/AvidaCPU_InstLib.hpp index 7af25091fa..1cecbf53ea 100644 --- a/include/emp/hardware/AvidaCPU_InstLib.hpp +++ b/include/emp/hardware/AvidaCPU_InstLib.hpp @@ -104,11 +104,11 @@ namespace emp { static void Inst_Call(hardware_t & hw, const inst_t & inst) { // Make sure function exists and is still in place. size_t def_pos = (size_t) hw.fun_starts[inst.args[0]]; - if (def_pos >= hw.genome.sequence.size() - || hw.GetScopeType(hw.genome.sequence[def_pos].id) != ScopeType::FUNCTION) return; + if (def_pos >= hw.genome.size() + || hw.GetScopeType(hw.genome[def_pos].id) != ScopeType::FUNCTION) return; // Go back into the function's original scope (call is in that scope) - size_t fun_scope = hw.genome.sequence[def_pos].args[1]; + size_t fun_scope = hw.genome[def_pos].args[1]; if (hw.UpdateScope(fun_scope, ScopeType::FUNCTION) == false) return; hw.call_stack.push_back(hw.inst_ptr+1); // Back up the call position hw.inst_ptr = def_pos+1; // Jump to the function body (will adavance) diff --git a/include/emp/hardware/AvidaGP.hpp b/include/emp/hardware/AvidaGP.hpp index b870c86dc4..a93b9dbc0d 100644 --- a/include/emp/hardware/AvidaGP.hpp +++ b/include/emp/hardware/AvidaGP.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2017 + * @date 2017-2021. * * @file AvidaGP.hpp * @brief This is a simple, efficient CPU for and applied version of Avida. @@ -32,6 +32,7 @@ #include "../tools/string_utils.hpp" #include "AvidaCPU_InstLib.hpp" +#include "Genome.hpp" namespace emp { @@ -43,15 +44,14 @@ namespace emp { static constexpr size_t STACK_CAP = 16; // Max size for stacks. struct Instruction; - struct Genome; using this_t = AvidaCPU_Base; using hardware_t = HARDWARE; using inst_t = Instruction; - using genome_t = Genome; - using arg_t = size_t; // All arguments are non-negative ints (indecies!) - + using arg_t = size_t; // Args are indecies. using inst_lib_t = AvidaCPU_InstLib; + using genome_t = Genome; + using stack_t = emp::vector; using arg_set_t = emp::array; @@ -66,50 +66,17 @@ namespace emp { Instruction & operator=(const Instruction &) = default; Instruction & operator=(Instruction &&) = default; - bool operator<(const Instruction & other) const { - return std::tie(id, args) < std::tie(other.id, other.args); + bool operator<(const Instruction & in) const { + return std::tie(id, args) < std::tie(in.id, in.args); } + bool operator==(const Instruction & in) const { return id == in.id && args == in.args; } + bool operator!=(const Instruction & in) const { return !(*this == in); } + bool operator>(const Instruction & in) const { return in < *this; } + bool operator>=(const Instruction & in) const { return !(*this < in); } + bool operator<=(const Instruction & in) const { return !(in < *this); } void Set(size_t _id, size_t _a0=0, size_t _a1=0, size_t _a2=0) { id = _id; args[0] = _a0; args[1] = _a1; args[2] = _a2; } - - bool operator==(const Instruction & in) const { return id == in.id && args == in.args; } - }; - - struct Genome { - using sequence_t = emp::vector; - - Ptr inst_lib; - sequence_t sequence; - - Genome() = default; - Genome(Ptr _inst_lib, const sequence_t & _seq=sequence_t(0)) - : inst_lib(_inst_lib), sequence(_seq) { ; } - Genome(const inst_lib_t & _inst_lib, const sequence_t & _seq=sequence_t(0)) - : inst_lib(&_inst_lib), sequence(_seq) { ; } - Genome(const Genome &) = default; - Genome(Genome &&) = default; - ~Genome() { ; } - - size_t Hash() const { - std::size_t seed = sequence.size(); - for(auto& i : sequence) { - seed ^= i.id + 0x9e3779b9 + (seed << 6) + (seed >> 2); - } - return seed; - } - struct hash_t { size_t operator()(const Genome & g) const { return g.Hash(); } }; - - - Genome & operator=(const Genome &) = default; - Genome & operator=(Genome &&) = default; - - bool operator==(const Genome& other) const { return sequence == other.sequence; } - bool operator!=(const Genome& other) const { return sequence != other.sequence; } - bool operator< (const Genome& other) const { return sequence < other.sequence; } - bool operator<=(const Genome& other) const { return sequence <= other.sequence; } - bool operator> (const Genome& other) const { return sequence > other.sequence; } - bool operator>=(const Genome& other) const { return sequence >= other.sequence; } }; struct ScopeInfo { @@ -179,7 +146,7 @@ namespace emp { if (CurScopeType() == ScopeType::LOOP) { inst_ptr = scope_stack.back().start_pos; // Move back to the beginning of the loop. ExitScope(); // Clear former scope - ProcessInst( genome.sequence[inst_ptr] ); // Process loops start again. + ProcessInst( genome[inst_ptr] ); // Process loops start again. return false; // We did NOT enter the new scope. } @@ -187,13 +154,13 @@ namespace emp { if (CurScopeType() == ScopeType::FUNCTION) { // @CAO Make sure we exit multiple scopes if needed to close the function... inst_ptr = call_stack.back(); // Return from the function call. - if (inst_ptr >= genome.sequence.size()) { // Test if call occured at end of genome. + if (inst_ptr >= genome.GetSize()) { // Test if call occured at end of genome. ResetIP(); // ...and reset to the begnning if so. } else { call_stack.pop_back(); // Clear the return position from the call stack. ExitScope(); // Leave the function scope. } - ProcessInst( genome.sequence[inst_ptr] ); // Process the new instruction instead. + ProcessInst( genome[inst_ptr] ); // Process the new instruction instead. return false; // We did NOT enter the new scope. } @@ -210,9 +177,9 @@ namespace emp { if (CurScope() < scope) return; // Only continue if break is relevant for current scope. ExitScope(); - while (inst_ptr+1 < genome.sequence.size()) { + while (inst_ptr+1 < genome.GetSize()) { inst_ptr++; - const size_t test_scope = InstScope(genome.sequence[inst_ptr]); + const size_t test_scope = InstScope(genome[inst_ptr]); // If this instruction sets the scope AND it's outside the one we want to end, stop here! if (test_scope && test_scope <= scope) { @@ -236,11 +203,11 @@ namespace emp { } /// Create a default AvidaCPU (no genome sequence, default instruction set) - AvidaCPU_Base() : AvidaCPU_Base(Genome(inst_lib_t::DefaultInstLib())) { ; } + AvidaCPU_Base() : AvidaCPU_Base(genome_t(inst_lib_t::DefaultInstLib())) { ; } /// Create an AvidaCPU with a specified instruction set (but no genome sequence) - AvidaCPU_Base(Ptr inst_lib) : AvidaCPU_Base(Genome(inst_lib)) { ; } - AvidaCPU_Base(const inst_lib_t & inst_lib) : AvidaCPU_Base(Genome(&inst_lib)) { ; } + AvidaCPU_Base(Ptr inst_lib) : AvidaCPU_Base(genome_t(inst_lib)) { ; } + AvidaCPU_Base(const inst_lib_t & inst_lib) : AvidaCPU_Base(genome_t(&inst_lib)) { ; } /// Copy constructor AvidaCPU_Base(const AvidaCPU_Base &) = default; @@ -261,9 +228,9 @@ namespace emp { /// Reset the entire CPU to a starting state, without a genome. void Reset() { - genome.sequence.resize(0); // Clear out genome - traits.resize(0); // Clear out traits - ResetHardware(); // Reset the full hardware + genome.resize(0); // Clear out genome + traits.resize(0); // Clear out traits + ResetHardware(); // Reset the full hardware } /// Reset just the CPU hardware, but keep the genome and traits. @@ -296,12 +263,12 @@ namespace emp { } // Accessors - Ptr GetInstLib() const { return genome.inst_lib; } - inst_t GetInst(size_t pos) const { return genome.sequence[pos]; } - inst_t& operator[](size_t pos) {return genome.sequence[pos]; } // Alias for compatability with tools + Ptr GetInstLib() const { return genome.GetInstLib(); } + inst_t GetInst(size_t pos) const { return genome[pos]; } + inst_t& operator[](size_t pos) {return genome[pos]; } // Alias for compatability with tools const genome_t & GetGenome() const { return genome; } - const size_t GetSize() const { return genome.sequence.size(); } - const size_t size() const { return GetSize(); } // Alias for compatability with tools + size_t GetSize() const { return genome.GetSize(); } + size_t size() const { return GetSize(); } // Alias for compatability with tools double GetReg(size_t id) const { return regs[id]; } double GetInput(int id) const { return Find(inputs, id, 0.0); } const std::unordered_map & GetInputs() const { return inputs; } @@ -315,7 +282,7 @@ namespace emp { emp::vector GetScopeStack() const { return scope_stack; } size_t CurScope() const { return scope_stack.back().scope; } ScopeType CurScopeType() const { return scope_stack.back().type; } - ScopeType GetScopeType(size_t id) { return genome.inst_lib->GetScopeType(id); } + ScopeType GetScopeType(size_t id) { return GetInstLib()->GetScopeType(id); } emp::vector GetRegStack() const { return reg_stack; } emp::vector GetCallStack() const { return call_stack; } size_t GetNumErrors() const { return errors; } @@ -323,9 +290,9 @@ namespace emp { const emp::vector & GetTraits() { return traits; } size_t GetNumTraits() const { return traits.size(); } - void SetInst(size_t pos, const inst_t & inst) { genome.sequence[pos] = inst; } + void SetInst(size_t pos, const inst_t & inst) { genome[pos] = inst; } void SetInst(size_t pos, size_t id, size_t a0=0, size_t a1=0, size_t a2=0) { - genome.sequence[pos].Set(id, a0, a1, a2); + genome[pos].Set(id, a0, a1, a2); } void SetGenome(const genome_t & g) { genome = g; } void SetReg(size_t id, double val) { regs[id] = val; } @@ -359,31 +326,44 @@ namespace emp { void PushTrait(double val) { traits.push_back(val); } inst_t GetRandomInst(Random & rand) { - return inst_t(rand.GetUInt(genome.inst_lib->GetSize()), + return inst_t(rand.GetUInt(GetInstLib()->GetSize()), rand.GetUInt(CPU_SIZE), rand.GetUInt(CPU_SIZE), rand.GetUInt(CPU_SIZE)); } void RandomizeInst(size_t pos, Random & rand) { SetInst(pos, GetRandomInst(rand) ); } + /// Add a new instruction to the end of the genome, by ID and args. void PushInst(size_t id, size_t a0=0, size_t a1=0, size_t a2=0) { - genome.sequence.emplace_back(id, a0, a1, a2); + genome.emplace_back(id, a0, a1, a2); } + + /// Add a new instruction to the end of the genome, by NAME and args. void PushInst(const std::string & name, size_t a0=0, size_t a1=0, size_t a2=0) { - size_t id = genome.inst_lib->GetID(name); - genome.sequence.emplace_back(id, a0, a1, a2); + PushInst(GetInstLib()->GetID(name), a0, a1, a2); } - void PushInst(const Instruction & inst) { genome.sequence.emplace_back(inst); } - void PushInst(Instruction && inst) { genome.sequence.emplace_back(inst); } + + /// Add a specified new instruction to the end of the genome. + void PushInst(const Instruction & inst) { genome.emplace_back(inst); } + + /// Add multiple copies of a specified instruction to the end of the genome. + void PushInst(const Instruction & inst, size_t count) { + genome.reserve(genome.size() + count); + for (size_t i = 0; i < count; i++) genome.emplace_back(inst); + } + + /// Add one or more default instructions to the end of the genome. + void PushDefaultInst(size_t count=1) { PushInst( Instruction(0), count ); } + void PushInstString(std::string info) { - size_t id = genome.inst_lib->GetID( string_pop(info) ); + size_t id = GetInstLib()->GetID( string_pop(info) ); size_t arg1 = info.size() ? from_string(string_pop(info)) : 0; size_t arg2 = info.size() ? from_string(string_pop(info)) : 0; size_t arg3 = info.size() ? from_string(string_pop(info)) : 0; PushInst(id, arg1, arg2, arg3); } - void PushRandom(Random & rand, const size_t count=1) { + void PushRandom(Random & random, const size_t count=1) { for (size_t i = 0; i < count; i++) { - PushInst(GetRandomInst(rand)); + PushInst(GetRandomInst(random)); } } @@ -392,16 +372,16 @@ namespace emp { bool Load(const std::string & filename) { std::ifstream is(filename); return Load(is); } /// Process a specified instruction, provided by the caller. - void ProcessInst(const inst_t & inst) { genome.inst_lib->ProcessInst(ToPtr(this), inst); } + void ProcessInst(const inst_t & inst) { GetInstLib()->ProcessInst(ToPtr(this), inst); } /// Determine the scope associated with a particular instruction. size_t InstScope(const inst_t & inst) const; /// Process the NEXT instruction pointed to be the instruction pointer void SingleProcess() { - emp_assert(genome.sequence.size() > 0); // A genome must exist to be processed. - if (inst_ptr >= genome.sequence.size()) ResetIP(); - genome.inst_lib->ProcessInst(ToPtr(this), genome.sequence[inst_ptr]); + emp_assert(genome.GetSize() > 0); // A genome must exist to be processed. + if (inst_ptr >= genome.GetSize()) ResetIP(); + GetInstLib()->ProcessInst(ToPtr(this), genome[inst_ptr]); inst_ptr++; } @@ -415,6 +395,16 @@ namespace emp { void PrintGenome(std::ostream & os=std::cout) const; void PrintGenome(const std::string & filename) const; + /// Print out a short version of the genome as a single string. + void PrintSymbols(std::ostream & os=std::cout) const; + + /// Convert the current state to a string. + std::string ToString() const { + std::stringstream ss; + PrintSymbols(ss); + return ss.str(); + } + /// Figure out which instruction is going to actually be run next SingleProcess() size_t PredictNextInst() const; @@ -444,14 +434,14 @@ namespace emp { template size_t AvidaCPU_Base::InstScope(const typename AvidaCPU_Base::inst_t & inst) const { - if (genome.inst_lib->GetScopeType(inst.id) == ScopeType::NONE) return 0; - return inst.args[ genome.inst_lib->GetScopeArg(inst.id) ] + 1; + if (GetInstLib()->GetScopeType(inst.id) == ScopeType::NONE) return 0; + return inst.args[ GetInstLib()->GetScopeArg(inst.id) ] + 1; } template void AvidaCPU_Base::PrintInst(const inst_t & inst, std::ostream & os) const { - os << genome.inst_lib->GetName(inst.id); - const size_t num_args = genome.inst_lib->GetNumArgs(inst.id); + os << GetInstLib()->GetName(inst.id); + const size_t num_args = GetInstLib()->GetNumArgs(inst.id); for (size_t i = 0; i < num_args; i++) { os << ' ' << inst.args[i]; } @@ -461,7 +451,7 @@ namespace emp { void AvidaCPU_Base::PrintGenome(std::ostream & os) const { size_t cur_scope = 0; - for (const inst_t & inst : genome.sequence) { + for (const inst_t & inst : genome) { size_t new_scope = InstScope(inst); if (new_scope) { @@ -491,13 +481,30 @@ namespace emp { of.close(); } + template + void AvidaCPU_Base::PrintSymbols(std::ostream & os) const { + // Example output: t(12)u()b(A5C)m(8) + + for (const inst_t & inst : genome) { + os << GetInstLib()->GetSymbol(inst.id) << "["; + const size_t num_args = GetInstLib()->GetNumArgs(inst.id); + for (size_t i = 0; i < num_args; i++) { + size_t arg_id = inst.args[i]; + if (arg_id < 10) os << arg_id; + else os << ('A' + (char) (arg_id - 10)); + } + os << "]"; + } + os << '\n'; + } + template size_t AvidaCPU_Base::PredictNextInst() const { // Determine if we are changing scope. size_t new_scope = CPU_SIZE+1; // Default to invalid scope. - if (inst_ptr >= genome.sequence.size()) new_scope = 0; + if (inst_ptr >= genome.GetSize()) new_scope = 0; else { - size_t inst_scope = InstScope(genome.sequence[inst_ptr]); + size_t inst_scope = InstScope(genome[inst_ptr]); if (inst_scope) new_scope = inst_scope; } @@ -512,12 +519,12 @@ namespace emp { // If we are at the end of a function, assume we will jump back to the call. if (CurScopeType() == ScopeType::FUNCTION) { size_t next_pos = call_stack.back(); - if (next_pos >= genome.sequence.size()) next_pos = 0; + if (next_pos >= genome.GetSize()) next_pos = 0; return next_pos; } // If we have run past the end of the genome, we will start over. - if (inst_ptr >= genome.sequence.size()) return 0; + if (inst_ptr >= genome.GetSize()) return 0; // Otherwise, we exit the scope normally. return inst_ptr; @@ -541,8 +548,8 @@ namespace emp { if (inst_ptr != next_inst) os << "(-> " << next_inst << ")"; os << " scope:" << CurScope() << " ("; - if (next_inst < genome.sequence.size()) { // For interpreter mode - PrintInst(genome.sequence[next_inst], os); + if (next_inst < genome.GetSize()) { // For interpreter mode + PrintInst(genome[next_inst], os); } os << ")" << " errors: " << errors @@ -562,8 +569,8 @@ namespace emp { using typename base_t::inst_lib_t; AvidaGP(const genome_t & in_genome) : AvidaCPU_Base(in_genome) { ; } - AvidaGP(Ptr inst_lib) : AvidaCPU_Base(Genome(inst_lib)) { ; } - AvidaGP(const inst_lib_t & inst_lib) : AvidaCPU_Base(Genome(&inst_lib)) { ; } + AvidaGP(Ptr inst_lib) : AvidaCPU_Base(genome_t(inst_lib)) { ; } + AvidaGP(const inst_lib_t & inst_lib) : AvidaCPU_Base(genome_t(&inst_lib)) { ; } AvidaGP() = default; AvidaGP(const AvidaGP &) = default; diff --git a/include/emp/hardware/EventDrivenGP.hpp b/include/emp/hardware/EventDrivenGP.hpp index be1a39b8d3..66beee5bf4 100644 --- a/include/emp/hardware/EventDrivenGP.hpp +++ b/include/emp/hardware/EventDrivenGP.hpp @@ -2057,7 +2057,7 @@ namespace emp { const auto & tag = inst.affinity; const double val = ( - tag.GetDouble() / tag.MaxDouble() + tag.GetValue() / tag.GetNumStates() ) * (max - min) - min; state.SetLocal(inst.args[0], val); diff --git a/include/emp/hardware/Genome.hpp b/include/emp/hardware/Genome.hpp new file mode 100644 index 0000000000..2763df294e --- /dev/null +++ b/include/emp/hardware/Genome.hpp @@ -0,0 +1,91 @@ +/** + * @note This file is part of Empirical, https://github.com/devosoft/Empirical + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file Genome.hpp + * @brief This is a simple, container for a series of instructions. + * + */ + +#ifndef EMP_GENOME_H +#define EMP_GENOME_H + +#include "../base/Ptr.hpp" + + +#include "../base/array.hpp" +#include "../base/vector.hpp" +#include "../datastructs/map_utils.hpp" +#include "../io/File.hpp" +#include "../math/Random.hpp" +#include "../tools/string_utils.hpp" + +namespace emp { + + template < typename INST_T, typename INST_LIB_T, typename SEQUENCE_T=emp::vector > + class Genome : public SEQUENCE_T { + private: + emp::Ptr inst_lib; + + public: + Genome() = default; + Genome(emp::Ptr _inst_lib, const SEQUENCE_T & _seq=SEQUENCE_T()) + : SEQUENCE_T(_seq), inst_lib(_inst_lib) { ; } + Genome(const INST_LIB_T & _inst_lib, const SEQUENCE_T & _seq=SEQUENCE_T()) + : SEQUENCE_T(_seq), inst_lib(&_inst_lib) { ; } + Genome(const Genome &) = default; + Genome(Genome &&) = default; + ~Genome() { ; } + + Genome & operator=(const Genome &) = default; + Genome & operator=(Genome &&) = default; + + auto GetInstLib() const { return inst_lib; } + + size_t GetSize() const { return SEQUENCE_T::size(); } + void Resize(size_t new_size) { SEQUENCE_T::resize(new_size); } + + bool operator==(const Genome& other) const { + if (SEQUENCE_T::size() != other.size()) return false; + auto it1 = SEQUENCE_T::begin(); + auto it2 = other.begin(); + while (it1 != SEQUENCE_T::end() && it2 != other.end()) { + if (*it1 != *it2) return false; + ++it1; ++it2; + } + return true; + } + bool operator!=(const Genome& other) const { return !(*this == other); } + + /// Compare genomes using an equivilent of alphabetical order. + bool operator< (const Genome& other) const { + // Step through the instructions + auto it1 = SEQUENCE_T::begin(); + auto it2 = other.begin(); + while (it1 != SEQUENCE_T::end() && it2 != other.end()) { + if (*it1 != *it2) return *it1 < *it2; // The first difference determines result + ++it1; ++it2; + } + // If sequences have been identical so far, shorter comes first (tie is false) + return SEQUENCE_T::size() < other.size(); + } + + bool operator> (const Genome& other) const { return other < *this; } + bool operator<=(const Genome& other) const { return !(other > *this); } + bool operator>=(const Genome& other) const { return !(other < *this); } + + size_t Hash() const { + std::size_t seed = GetSize(); + for(auto& i : *this) { + seed ^= i.id + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; + } + struct hash_t { size_t operator()(const Genome & g) const { return g.Hash(); } }; + + }; + +} + +#endif diff --git a/include/emp/hardware/InstLib.hpp b/include/emp/hardware/InstLib.hpp index 7d51daba22..9a13f33165 100644 --- a/include/emp/hardware/InstLib.hpp +++ b/include/emp/hardware/InstLib.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2017 + * @date 2017-2021. * * @file InstLib.hpp * @brief This file maintains information about instructions availabel in virtual hardware. @@ -48,11 +48,14 @@ namespace emp { ScopeType scope_type; ///< How does this instruction affect scoping? size_t scope_arg; ///< Which arg indicates new scope (if any). inst_properties_t properties; ///< Are there any generic properties associated with this inst def? + char symbol; ///< Unique symbol for this instruction. InstDef(const std::string & _n, fun_t _fun, size_t _args, const std::string & _d, - ScopeType _s_type, size_t _s_arg, const inst_properties_t & _properties = inst_properties_t()) + ScopeType _s_type, size_t _s_arg, + const inst_properties_t & _properties = inst_properties_t(), + char _sym='?') : name(_n), fun_call(_fun), num_args(_args), desc(_d) - , scope_type(_s_type), scope_arg(_s_arg), properties(_properties) { ; } + , scope_type(_s_type), scope_arg(_s_arg), properties(_properties), symbol(_sym) { ; } InstDef(const InstDef &) = default; }; @@ -62,6 +65,11 @@ namespace emp { std::map name_map; ///< How do names link to instructions? std::map arg_map; ///< How are different arguments named? + /// Symbols to use when representing individual instructions (80). + std::string symbol_defaults = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*~_=,.|/\\><"; + char extra_symbol = '+'; ///< Symbol for more instructions than fit above. + emp::array symbol_map; ///< Map of symbols back to instruction IDs. + public: InstLib() : inst_lib(), inst_funs(), name_map(), arg_map() { ; } ///< Default Constructor InstLib(const InstLib &) = delete; ///< Copy Constructor @@ -92,22 +100,16 @@ namespace emp { /// Return the set of properties for the provided instruction ID. const inst_properties_t & GetProperties(size_t id) const { return inst_lib[id].properties; } + char GetSymbol(size_t id) const { return inst_lib[id].symbol; } + /// Does the given instruction ID have the given property value? bool HasProperty(size_t id, std::string property) const { return inst_lib[id].properties.count(property); } /// Get the number of instructions in this set. size_t GetSize() const { return inst_lib.size(); } - - /// Retrieve a unique letter associated with the specified instruction ID. - static constexpr char GetSymbol(size_t id) { - if (id < 26) return ('a' + id); - if (id < 52) return ('A' + (id - 26)); - if (id < 62) return ('0' + (id - 52)); - return '+'; - } - + bool IsInst(const std::string name) const { - return Has(name_map, name); + return Has(name_map, name); } /// Return the ID of the instruction that has the specified name. @@ -117,11 +119,9 @@ namespace emp { } /// Return the ID of the instruction associated with the specified symbol. - static constexpr size_t GetID(char symbol) { - if (symbol >= 'a' && symbol <= 'z') return (size_t) (symbol - 'a'); - if (symbol >= 'A' && symbol <= 'Z') return (size_t) (symbol - 'A' + 26); - if (symbol >= '0' && symbol <= '9') return (size_t) (symbol - '0' + 52); - return (size_t) 62; + size_t GetID(char symbol) { + emp_assert(symbol > 0); + return symbol_map[(size_t) symbol]; } /// Return the argument value associated with the provided keyword. @@ -136,7 +136,8 @@ namespace emp { /// @param num_args How many arguments does this function require? (default=0) /// @param desc A description of how this function operates. (default="") /// @param scope_type Type of scope does this instruction creates. (default=ScopeType::NONE) - /// @param scope_arg If instruction changes scope, which argument specified new scope? (default=-1) + /// @param scope_arg If instruction changes scope, which argument specifies new scope? (default=-1) + /// @param inst_properties Strings representing arbitrary properties associated with instruction void AddInst(const std::string & name, const fun_t & fun_call, size_t num_args=0, @@ -146,9 +147,11 @@ namespace emp { const inst_properties_t & inst_properties=inst_properties_t()) { const size_t id = inst_lib.size(); - inst_lib.emplace_back(name, fun_call, num_args, desc, scope_type, scope_arg, inst_properties); + const char symbol = (id < symbol_defaults.size()) ? symbol_defaults[id] : '+'; + inst_lib.emplace_back(name, fun_call, num_args, desc, scope_type, scope_arg, inst_properties, symbol); inst_funs.emplace_back(fun_call); name_map[name] = id; + symbol_map[(size_t) symbol] = id; } /// Specify a keyword and arg value. diff --git a/include/emp/io/File.hpp b/include/emp/io/File.hpp index cb012a5999..c95c19ad2a 100644 --- a/include/emp/io/File.hpp +++ b/include/emp/io/File.hpp @@ -1,15 +1,14 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2018-2019 + * @date 2018-2020. * * @file File.hpp * @brief The File object maintains a simple, in-memory file. * @note Status: BETA * - * @todo We need to modify this code to make sure File can also work with Emscripten, - * appropriately. Alternatively, we might want to have a more flexible file class - * that wraps this one. + * @todo We need to modify this code so that File can work with Emscripten. + * Alternatively, we might want to have a more flexible file class that wraps this one. * */ @@ -168,6 +167,14 @@ namespace emp { return *this; } + /// Test if a substring exists on ANY line of a file. + bool Contains(const std::string & pattern) const { + for (const std::string & line : lines) { + if (line.find(pattern) != std::string::npos) return true; + } + return false; + } + /// Convert this file into an std::set of lines (loses line ordering). std::set AsSet() const { std::set line_set; @@ -185,7 +192,7 @@ namespace emp { return *this; } - /// Purge functions that don't meet a certain criterion. + /// Purge all lines that don't the criterion function. File & KeepIf(const std::function & fun) { emp::vector new_lines; for (std::string & cur_line : lines) { @@ -195,6 +202,20 @@ namespace emp { return *this; } + /// Keep only strings that contain a specific substring. + File & KeepIfContains(const std::string & pattern) { + return KeepIf( + [&pattern](const std::string & line){ return line.find(pattern) != std::string::npos; } + ); + } + + /// Remove all strings that contain a specific substring. + File & RemoveIfContains(const std::string & pattern) { + return KeepIf( + [&pattern](const std::string & line){ return line.find(pattern) == std::string::npos; } + ); + } + /// Remove all lines that are empty strings. File & RemoveEmpty() { return KeepIf( [](const std::string & str){ return (bool) str.size(); } ); diff --git a/include/emp/io/serialize.hpp b/include/emp/io/serialize.hpp index e07ada197a..1fe3a69871 100644 --- a/include/emp/io/serialize.hpp +++ b/include/emp/io/serialize.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2015-2019 + * @date 2015-2021. * * @file serialize.hpp * @brief Tools to save and load data from classes. diff --git a/include/emp/io/serialize_macros.hpp b/include/emp/io/serialize_macros.hpp index aa33ca9e04..9acfe6ee33 100644 --- a/include/emp/io/serialize_macros.hpp +++ b/include/emp/io/serialize_macros.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2015-2017 + * @date 2015-2021. * * @file serialize_macros.hpp * @brief Macros for simplifying to serialization of objects. @@ -12,7 +12,7 @@ #ifndef EMP_SERIALIZE_MACROS_H #define EMP_SERIALIZE_MACROS_H -#include "../base/macros.hpp" +#include "../meta/macros.hpp" #define EMP_SERIALIZE_INIT_VAR(VAR) VAR(emp::serialize::SetupLoad(pod, &VAR, true)) diff --git a/include/emp/matching/matchbin_metrics.hpp b/include/emp/matching/matchbin_metrics.hpp index 36a41c44b5..d72fbecb7f 100644 --- a/include/emp/matching/matchbin_metrics.hpp +++ b/include/emp/matching/matchbin_metrics.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020. + * @date 2019-2021. * * @file matchbin_metrics.hpp * @brief Metric structs that can be plugged into MatchBin. @@ -254,7 +254,7 @@ namespace emp { } inline static double calculate(const query_t& a, const tag_t& b) { - return (b - a).GetDouble() / emp::BitSet::MaxDouble(); + return (b - a).GetValue() / emp::BitSet::GetNumStates(); } }; @@ -281,8 +281,8 @@ namespace emp { } inline static double calculate(const query_t& a, const tag_t& b) { - constexpr double max_dist = emp::BitSet::MaxDouble() + 1.0; - return (b >= a ? (b - a).GetDouble() : max_dist) / max_dist; + constexpr double max_dist = emp::BitSet::GetNumStates(); + return (b >= a ? (b - a).GetValue() : max_dist) / max_dist; } }; @@ -312,10 +312,8 @@ namespace emp { } inline static double calculate(const query_t& a, const tag_t& b) { - constexpr double max_dist = ( - (emp::BitSet::MaxDouble() + 1.0) / 2.0 - ); - return std::min(a - b, b - a).GetDouble() / max_dist; + constexpr double max_dist = emp::BitSet::GetNumStates() / 2.0; + return std::min(a - b, b - a).GetValue() / max_dist; } }; @@ -344,9 +342,7 @@ namespace emp { } inline static double calculate(const query_t& a, const tag_t& b) { - return ( - a > b ? a - b : b - a - ).GetDouble() / emp::BitSet::MaxDouble(); + return (a > b ? a - b : b - a).GetValue() / emp::BitSet::GetNumStates(); } }; @@ -1000,14 +996,19 @@ namespace emp { using query_t = typename Metric::query_t; using tag_t = typename Metric::query_t; - inline const static internal::lookup_holder held{}; + //inline const static internal::lookup_holder held{}; + + static const internal::lookup_holder & GetLookupHolder() { + static const internal::lookup_holder held{}; + return held; + } std::string name() const override { - return emp::to_string("Uniformified ", held.metric.name()); + return emp::to_string("Uniformified ", GetLookupHolder().metric.name()); } inline static double calculate(const query_t& a, const tag_t& b) { - return held.lookup(held.metric(a, b)); + return GetLookupHolder().lookup(GetLookupHolder().metric(a, b)); } double operator()(const query_t& a, const tag_t& b) const override { diff --git a/include/emp/matching/matchbin_selectors.hpp b/include/emp/matching/matchbin_selectors.hpp index 8d7b7b61d9..dc39589e6b 100644 --- a/include/emp/matching/matchbin_selectors.hpp +++ b/include/emp/matching/matchbin_selectors.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020. + * @date 2019-2021. * * @file matchbin_selectors.hpp * @brief Selector structs that can be plugged into MatchBin. @@ -607,7 +607,8 @@ namespace emp { const auto partition = std::partition( std::begin(scores), std::end(scores), - [&scores, lock_in, stochastic](const auto& pair){ + // [&scores, lock_in, stochastic](const auto& pair){ + [lock_in, stochastic](const auto& pair){ return pair.second < lock_in + stochastic; } ); @@ -617,7 +618,8 @@ namespace emp { std::begin(scores), partition, std::back_inserter(probabilities), - [&scores, lock_in, stochastic](const auto& pair){ + // [&scores, lock_in, stochastic](const auto& pair){ + [lock_in, stochastic](const auto& pair){ // goal: // RAW SCORE: 0.0 ... lock_in ... lock_in + stochastic ... 1.0 // INTERMEDIATE: 0.0 ... 0.0 ... -> ... 1.0 ... ... 1.0 diff --git a/include/emp/math/Random.hpp b/include/emp/math/Random.hpp index 129e5dfcc9..c370fde7b7 100644 --- a/include/emp/math/Random.hpp +++ b/include/emp/math/Random.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2015-2020. + * @date 2015-2021. * * @file Random.hpp * @brief A versatile and non-patterned pseudo-random-number generator. @@ -18,6 +18,7 @@ #include #include "../base/assert.hpp" +#include "../base/Ptr.hpp" #include "../bits/bitset_utils.hpp" #include "Range.hpp" @@ -41,10 +42,11 @@ namespace emp { static constexpr const uint64_t RAND_CAP = 4294967296; // 2^32 static constexpr const uint64_t STEP_SIZE = 0xb5ad4eceda1ce2a9; ///< Weyl sequence step size + static constexpr const unsigned char BYTE1 = (unsigned char) 1; /// Basic Random number /// Returns a random number [0, RAND_CAP) - uint32_t Get() { + uint32_t Get() noexcept { value *= value; // Square the current value. value += (weyl_state += STEP_SIZE); // Take a step in the Weyl sequence value = (value>>32) | (value<<32); // Return the middle of the value @@ -53,21 +55,21 @@ namespace emp { public: /// Set up the random generator object with an optional seed value. - Random(const int seed = -1) { + Random(const int seed = -1) noexcept { ResetSeed(seed); // Calls init() } ~Random() { ; } /// Advance pseudorandom number generation engine one step. - void StepEngine() { Get(); } + void StepEngine() noexcept { Get(); } /// @return The current seed used to initialize this pseudo-random sequence. - inline uint64_t GetSeed() const { return original_seed; } + inline uint64_t GetSeed() const noexcept { return original_seed; } /// Starts a new sequence of pseudo random numbers. A negative seed means that the random /// number generator gets its seed from the current system time and the process memory. - void ResetSeed(const int64_t seed) { + void ResetSeed(const int64_t seed) noexcept { // If the provided seed is <= 0, choose a unique seed based on time and memory location. if (seed <= 0) { uint64_t seed_time = (uint64_t) time(NULL); @@ -88,54 +90,74 @@ namespace emp { // Random Number Generation ///////////////////////////////////////////////// /// @return A pseudo-random double value between 0.0 and 1.0 - inline double GetDouble() { return Get() / (double) RAND_CAP; } + inline double GetDouble() noexcept { return Get() / (double) RAND_CAP; } /// @return A pseudo-random double value between 0.0 and max - inline double GetDouble(const double max) { return GetDouble() * max; } + inline double GetDouble(const double max) noexcept { return GetDouble() * max; } /// @return A pseudo-random double value between min and max - inline double GetDouble(const double min, const double max) { + inline double GetDouble(const double min, const double max) noexcept { return GetDouble() * (max - min) + min; } /// @return A pseudo-random double in the provided range. - inline double GetDouble(const Range range) { + inline double GetDouble(const Range range) noexcept { return GetDouble(range.GetLower(), range.GetUpper()); } /// @return A pseudo-random 32-bit (4 byte) unsigned int value. - inline uint32_t GetUInt() { - return Get(); - } + inline uint32_t GetUInt() noexcept { return Get(); } /// @return A pseudo-random 32-bit unsigned int value between 0 and max template - inline uint32_t GetUInt(const T max) { + inline uint32_t GetUInt(const T max) noexcept { return static_cast(GetDouble() * static_cast(max)); } /// @return A pseudo-random 32-bit unsigned int value between min and max template - inline uint32_t GetUInt(const T1 min, const T2 max) { + inline uint32_t GetUInt(const T1 min, const T2 max) noexcept { return GetUInt((uint32_t) max - (uint32_t) min) + (uint32_t) min; } /// @return A pseudo-random 32-bit unsigned int value in the provided range. template - inline uint32_t GetUInt(const Range range) { + inline uint32_t GetUInt(const Range range) noexcept { return GetUInt(range.GetLower(), range.GetUpper()); } + /// @return A pseudo-random 32 bits (unsigned int) with a 12.5% chance of each bit being 1. + inline uint32_t GetBits12_5() noexcept { return Get() & Get() & Get(); } + + /// @return A pseudo-random 32 bits (unsigned int) with a 25% chance of each bit being 1. + inline uint32_t GetBits25() noexcept { return Get() & Get(); } + + /// @return A pseudo-random 32 bits (unsigned int) with a 37.5% chance of each bit being 1. + inline uint32_t GetBits37_5() noexcept { return (Get() | Get()) & Get(); } + + /// @return A pseudo-random 32 bits (unsigned int) with a 50% chance of each bit being 1. + inline uint32_t GetBits50() noexcept { return Get(); } + + /// @return A pseudo-random 32 bits (unsigned int) with a 62.5% chance of each bit being 1. + inline uint32_t GetBits62_5() noexcept { return (Get() & Get()) | Get(); } + + /// @return A pseudo-random 32 bits (unsigned int) with a 75% chance of each bit being 1. + inline uint32_t GetBits75() noexcept { return Get() | Get(); } + + /// @return A pseudo-random 32 bits (unsigned int) with a 87.5% chance of each bit being 1. + inline uint32_t GetBits87_5() noexcept { return Get() | Get() | Get(); } + + /// @return A pseudo-random 64-bit (8 byte) unsigned int value. - inline uint64_t GetUInt64() { + inline uint64_t GetUInt64() noexcept { return ( static_cast(GetUInt()) << 32 ) + static_cast(GetUInt()); } /// @return A pseudo-random 64-bit unsigned int value between 0 and max - inline uint64_t GetUInt64(const uint64_t max) { + inline uint64_t GetUInt64(const uint64_t max) noexcept { if (max <= RAND_CAP) return (uint64_t) GetUInt(max); // Don't need extra precision. size_t mask = emp::MaskUsed(max); // Create a mask for just the bits we need. @@ -147,34 +169,179 @@ namespace emp { /// @return A pseudo-random 32-bit (4 byte) int value between 0 and max - inline int32_t GetInt(const int32_t max) { + inline int32_t GetInt(const int32_t max) noexcept { return static_cast(GetUInt((uint32_t) max)); } /// @return A pseudo-random 32-bit (4 byte) int value between min and max - inline int32_t GetInt(const int min, const int max) { return GetInt(max - min) + min; } + inline int32_t GetInt(const int min, const int max) noexcept { return GetInt(max - min) + min; } /// @return A pseudo-random 32-bit (4 byte) int value in range - inline int32_t GetInt(const Range range) { + inline int32_t GetInt(const Range range) noexcept { return GetInt(range.GetLower(), range.GetUpper()); } + /// Enumeration for common probabilities. + /// (not class, so can be referred to elsewhere as e.g., Random::PROB_50) + enum Prob { PROB_0 = 0, PROB_12_5 = 125, + PROB_25 = 250, PROB_37_5 = 375, + PROB_50 = 500, PROB_62_5 = 625, + PROB_75 = 750, PROB_87_5 = 875, + PROB_100 = 1000 }; + + /// Shortcut type for all functions that deal witch chunks of memory. + using mem_ptr_t = emp::Ptr; + + /// Randomize a contiguous segment of memory. + void RandFill(mem_ptr_t dest, const size_t num_bytes) noexcept { + dest.FillMemoryFunction( num_bytes, [this](){ return Get(); } ); + } /// Randomize a contiguous segment of memory. - void RandFill(unsigned char * dest, const size_t num_bytes) { - size_t leftover = num_bytes % 4; - size_t limit = num_bytes - leftover; - - // Fill out random bytes in groups of four. - for (size_t byte = 0; byte < limit; byte += 4) { - uint32_t rnd = Get(); - std::memcpy(dest+byte, &rnd, 4); + template + void RandFillP(mem_ptr_t dest, const size_t num_bytes) noexcept { + if constexpr (PROB == PROB_0) { + dest.FillMemoryFunction( num_bytes, [](){ return 0; } ); + } else if constexpr (PROB == PROB_12_5) { + dest.FillMemoryFunction( num_bytes, [this](){ return GetBits12_5(); } ); + } else if constexpr (PROB == PROB_25) { + dest.FillMemoryFunction( num_bytes, [this](){ return GetBits25(); } ); + } else if constexpr (PROB == PROB_37_5) { + dest.FillMemoryFunction( num_bytes, [this](){ return GetBits37_5(); } ); + } else if constexpr (PROB == PROB_50) { + dest.FillMemoryFunction( num_bytes, [this](){ return GetBits50(); } ); + } else if constexpr (PROB == PROB_62_5) { + dest.FillMemoryFunction( num_bytes, [this](){ return GetBits62_5(); } ); + } else if constexpr (PROB == PROB_75) { + dest.FillMemoryFunction( num_bytes, [this](){ return GetBits75(); } ); + } else if constexpr (PROB == PROB_87_5) { + dest.FillMemoryFunction( num_bytes, [this](){ return GetBits87_5(); } ); + } else if constexpr (PROB == PROB_100) { + dest.FillMemoryFunction( num_bytes, [](){ return (size_t) -1; } ); + } + } + + /// Randomize a contiguous segment of memory between specified bit positions. + template + void RandFillP(mem_ptr_t dest, [[maybe_unused]] const size_t num_bytes, size_t start_bit, size_t stop_bit) noexcept + { + emp_assert(start_bit <= stop_bit); + emp_assert(stop_bit <= num_bytes*8); + + const size_t start_byte_id = start_bit >> 3; // At which byte do we start? + const size_t end_byte_id = stop_bit >> 3; // At which byte do we stop? + const size_t start_bit_id = start_bit & 7; // Which bit to start at in byte? + const size_t end_bit_id = stop_bit & 7; // Which bit to stop before in byte? + constexpr double p = ((double) PROB) / 1000.0; // Determine actual probability of a 1 + + // If the start byte and end byte are the same, just fill those in. + if (start_byte_id == end_byte_id) { + for (size_t i = start_bit_id; i < end_bit_id; ++i) { + uint8_t mask = (uint8_t) (1 << i); + if (P(p)) dest[start_byte_id] |= mask; + else dest[start_byte_id] &= ~mask; + } + return; + } + + const uint8_t start_byte = dest[start_byte_id]; // Save first byte to restore bits. + + // Randomize the full bits we need to use. + RandFillP(dest + start_byte_id, end_byte_id - start_byte_id); + + // If we are not starting at the beginning of a byte, restore missing bits. + if (start_bit_id) { + const uint8_t mask = (uint8_t) ((1 << start_bit_id) - 1); // Signify how byte is divided. + (dest[start_byte_id] &= ~mask) |= (start_byte & mask); // Stitch together byte parts. + } + + // If we have a byte at the end to partially randomize, do so. + if (end_bit_id) { + uint8_t & end_byte = dest[end_byte_id]; // Grab reference to end byte + const uint8_t mask = (uint8_t) ((1 << end_bit_id) - 1); // Signify how byte is divided. + end_byte &= ~mask; // Clear out bits to be randomized. + for (size_t i = 0; i < end_bit_id; i++) { // Step through bits to flip. + if (P(p)) end_byte |= ((uint8_t) 1 << i); // Set appropriate bits. + } } + } + + // Shortcuts to randomize a contiguous segment of memory with fixed probabilities of a 1. + void RandFill0( mem_ptr_t dest, const size_t bytes) noexcept { RandFillP (dest, bytes); } + void RandFill12_5(mem_ptr_t dest, const size_t bytes) noexcept { RandFillP(dest, bytes); } + void RandFill25( mem_ptr_t dest, const size_t bytes) noexcept { RandFillP (dest, bytes); } + void RandFill37_5(mem_ptr_t dest, const size_t bytes) noexcept { RandFillP(dest, bytes); } + void RandFill50( mem_ptr_t dest, const size_t bytes) noexcept { RandFillP (dest, bytes); } + void RandFill62_5(mem_ptr_t dest, const size_t bytes) noexcept { RandFillP(dest, bytes); } + void RandFill75( mem_ptr_t dest, const size_t bytes) noexcept { RandFillP (dest, bytes); } + void RandFill87_5(mem_ptr_t dest, const size_t bytes) noexcept { RandFillP(dest, bytes); } + void RandFill100( mem_ptr_t dest, const size_t bytes) noexcept { RandFillP (dest, bytes); } + + void RandFill0( mem_ptr_t dest, const size_t bytes, size_t start_bit, size_t stop_bit) noexcept + { RandFillP (dest, bytes, start_bit, stop_bit); } + void RandFill12_5(mem_ptr_t dest, const size_t bytes, size_t start_bit, size_t stop_bit) noexcept + { RandFillP(dest, bytes, start_bit, stop_bit); } + void RandFill25( mem_ptr_t dest, const size_t bytes, size_t start_bit, size_t stop_bit) noexcept + { RandFillP (dest, bytes, start_bit, stop_bit); } + void RandFill37_5(mem_ptr_t dest, const size_t bytes, size_t start_bit, size_t stop_bit) noexcept + { RandFillP(dest, bytes, start_bit, stop_bit); } + void RandFill50( mem_ptr_t dest, const size_t bytes, size_t start_bit, size_t stop_bit) noexcept + { RandFillP (dest, bytes, start_bit, stop_bit); } + void RandFill62_5(mem_ptr_t dest, const size_t bytes, size_t start_bit, size_t stop_bit) noexcept + { RandFillP(dest, bytes, start_bit, stop_bit); } + void RandFill75( mem_ptr_t dest, const size_t bytes, size_t start_bit, size_t stop_bit) noexcept + { RandFillP (dest, bytes, start_bit, stop_bit); } + void RandFill87_5(mem_ptr_t dest, const size_t bytes, size_t start_bit, size_t stop_bit) noexcept + { RandFillP(dest, bytes, start_bit, stop_bit); } + void RandFill100( mem_ptr_t dest, const size_t bytes, size_t start_bit, size_t stop_bit) noexcept + { RandFillP (dest, bytes, start_bit, stop_bit); } + + /// Randomize a contiguous segment of memory with a given probability of each bit being on. + void RandFill(mem_ptr_t dest, const size_t num_bytes, const double p) noexcept { + // Try to find a shortcut if p allows.... + if (p == 0.0) return RandFill0(dest, num_bytes); + else if (p == 0.125) return RandFill12_5(dest, num_bytes); + else if (p == 0.25) return RandFill25(dest, num_bytes); + else if (p == 0.375) return RandFill37_5(dest, num_bytes); + else if (p == 0.5) return RandFill50(dest, num_bytes); + else if (p == 0.625) return RandFill62_5(dest, num_bytes); + else if (p == 0.75) return RandFill75(dest, num_bytes); + else if (p == 0.875) return RandFill87_5(dest, num_bytes); + else if (p == 1.0) return RandFill100(dest, num_bytes); + + // This is not a special value of P, so let's set each bit manually + // (slow, but good for now; ideally use closest faster option above and modify) + for (size_t i = 0; i < num_bytes; i++) dest[i] = GetByte(p); + } - // If we don't have a multiple of four, fill in the remaining. - if (leftover) { - uint32_t rnd = Get(); - std::memcpy(dest+num_bytes-leftover, &rnd, leftover); + /// Randomize a contiguous segment of memory with a given probability of each bit being on. + void RandFill(mem_ptr_t dest, const size_t num_bytes, const double p, + const size_t start_bit, const size_t stop_bit) noexcept { + emp_assert((stop_bit >> 3) <= num_bytes); + + // Try to find a shortcut if p allows.... + if (p == 0.0) return RandFill0(dest, num_bytes, start_bit, stop_bit); + else if (p == 0.125) return RandFill12_5(dest, num_bytes, start_bit, stop_bit); + else if (p == 0.25) return RandFill25(dest, num_bytes, start_bit, stop_bit); + else if (p == 0.375) return RandFill37_5(dest, num_bytes, start_bit, stop_bit); + else if (p == 0.5) return RandFill50(dest, num_bytes, start_bit, stop_bit); + else if (p == 0.625) return RandFill62_5(dest, num_bytes, start_bit, stop_bit); + else if (p == 0.75) return RandFill75(dest, num_bytes, start_bit, stop_bit); + else if (p == 0.875) return RandFill87_5(dest, num_bytes, start_bit, stop_bit); + else if (p == 1.0) return RandFill100(dest, num_bytes, start_bit, stop_bit); + + // This is not a special value of P, so let's set each bit manually + // (slow, but good for now; ideally use closest faster option above and modify) + size_t cur_byte = start_bit >> 3; + uint8_t cur_mask = (uint8_t) (1 << (start_bit & 7)); + for (size_t i = start_bit; i < stop_bit; i++) { + if (P(p)) dest[cur_byte] |= cur_mask; // Set the target bit. + else dest[cur_byte] &= ~cur_mask; // Clear out the target bit. + cur_mask <<= 1; // Move to the next bit. + if (!cur_mask) { // If the next bit is out of this byte... + cur_byte++; // move to the next byte. + cur_mask = 1; // reset the mask. + } } } @@ -183,18 +350,32 @@ namespace emp { /// Tests a random value [0,1) against a given probability p, and returns true of false. /// @param p The probability of the result being "true". - inline bool P(const double p) { + inline bool P(const double p) noexcept { emp_assert(p >= 0.0 && p <= 1.0, p); return (Get() < (p * RAND_CAP)); } + /// Full random byte with each bit being a one with a given probability. + unsigned char GetByte(const double p) noexcept { + unsigned char out_byte = 0; + if (P(p)) out_byte |= 1; + if (P(p)) out_byte |= 2; + if (P(p)) out_byte |= 4; + if (P(p)) out_byte |= 8; + if (P(p)) out_byte |= 16; + if (P(p)) out_byte |= 32; + if (P(p)) out_byte |= 64; + if (P(p)) out_byte |= 128; + return out_byte; + } + // Statistical functions //////////////////////////////////////////////////// // Distributions // /// Generate a random variable drawn from a unit normal distribution. - double GetRandNormal() { + double GetRandNormal() noexcept { // Draw from a Unit Normal Dist // Using Rejection Method and saving of initial exponential random variable double expRV2; @@ -280,7 +461,11 @@ namespace emp { /// Draw a sample (with replacement) from an input range, copying to the output range. template - void sample_with_replacement(ForwardIterator first, ForwardIterator last, OutputIterator ofirst, OutputIterator olast, RNG rng) { + void sample_with_replacement(ForwardIterator first, + ForwardIterator last, + OutputIterator ofirst, + OutputIterator olast, + RNG rng) noexcept { std::size_t range = std::distance(first, last); while(ofirst != olast) { *ofirst = *(first+rng(range)); diff --git a/include/emp/math/math.hpp b/include/emp/math/math.hpp index 792131a6e6..6a96f77fdc 100644 --- a/include/emp/math/math.hpp +++ b/include/emp/math/math.hpp @@ -227,6 +227,7 @@ namespace emp { /// A fast 2^x command. static constexpr double Pow2(double exp) { + if (exp > 1024) return std::numeric_limits::infinity(); return (exp < 0.0) ? (1.0/internal::Pow2_impl(-exp)) : internal::Pow2_impl(exp); } diff --git a/include/emp/meta/ConceptWrapper.hpp b/include/emp/meta/ConceptWrapper.hpp index 3fc40e5472..9b1280bdb1 100644 --- a/include/emp/meta/ConceptWrapper.hpp +++ b/include/emp/meta/ConceptWrapper.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2018-2019. + * @date 2018-2021. * * @file ConceptWrapper.hpp * @brief A template wrapper that will either enforce functionality or provide default functions. @@ -100,7 +100,7 @@ #include #include -#include "../base/macros.hpp" +#include "macros.hpp" #include "meta.hpp" #include "TypePack.hpp" diff --git a/include/emp/meta/StringType.hpp b/include/emp/meta/StringType.hpp deleted file mode 100644 index b2167acf8d..0000000000 --- a/include/emp/meta/StringType.hpp +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @note This file is part of Empirical, https://github.com/devosoft/Empirical - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2018-2019 - * - * @file StringType.hpp - * @brief A type that maintains compile-time information about a string sequence. - * - * DEVELOPER NOTES - * Mechanisms to add: - * resize - change the string length to the provided one. - * append - add another string type to the end of this one. - * upcase - change to all capital letters. - * downcase - change to all lowercase. - * - * Should this be merged into ValType? Or derived from it since it already has most of the - * functionalities that we want? - * - * Create a StringTypeID class that just has a size_t in it BUT includes a cast to std::string - * that will lookup the string associated with an ID. Should probably also have a more explicit - * ToString() as well. - * - * The static function that provides an ID for a string should call a different static function - * the first time an ID is created this other static function will store the string associated - * with the idea to simplify later retrieval. If called on a run-time string that it doesn't - * know it will also assign it a unique ID. The one potential problem is if the run-time version - * is used BEFORE the compile time version. As such, we need to always make sure to check if a - * string exists already when providing an ID. - */ - - -#ifndef EMP_STRING_TYPE_H -#define EMP_STRING_TYPE_H - -#include -#include - -#include "meta.hpp" -#include "TypeID.hpp" - -/// Convert a literal string to an instance of a StringType -#define EMP_TEXT_PACK(MSG) emp::StringPacketToStringType( [](){ return MSG; } ) - -/// Setup a type determined by a message. -#define EMP_TEXT_TYPE(TYPE_NAME, MSG) \ - auto emp_temp_ ## TYPE_NAME = EMP_TEXT_PACK(MSG); \ - using TYPE_NAME = decltype(emp_temp_ ## TYPE_NAME) -// I'd prefer for the body of EMP_TEXT_TYPE, but lambdas must be evaluated. -// decltype(emp::StringPacketToStringType( [](){ return MSG; } )) - -/// Convert a literal string to a unique value (counting up from 0 with each string) -#define EMP_TEXT_HASH(MSG) \ - ([](){ \ - constexpr auto temp = EMP_TEXT_PACK(MSG); \ - return emp::GetTypeID().GetID(); \ - }()) - - -namespace emp { - - // Generic form of StringType (actual types will be made using specializations below) - template struct StringType; - - // Template specialization to maintain full information of a non-empty string as a type. - template - struct StringType { - static constexpr char FIRST = C1; ///< Easy access to first character of string. - constexpr static int SIZE = 1+sizeof...(Cs); ///< Easy access to string length. - - using this_t = StringType; ///< The type of the current StringType - using pop = StringType; ///< StringType after removing the first char - template using push = StringType; ///< Add char to front of string - template using push_back = StringType; ///< Add char to back of string - - /// Does StringType contains the char C? - constexpr static bool Has(char C) { return (C==C1) | pop::Has(C); } - - /// Count the number of occurances of char C in StringType. - constexpr static int Count(int C) { return pop::Count(C) + (C==C1); } - - /// Determine the position at which C appears in StringType. - constexpr static int GetID(int C) { - if (C==C1) return 0; - if (!Has(C)) return -1; - return (1+pop::GetID(C)); - } - - /// Function to retrieve number of elements in StringType - constexpr static int GetSize() { return SIZE; } - - /// Determine if there are NO chars in an StringType - constexpr static bool IsEmpty() { return false; } - - /// Determine if all chars in StringType are different from each other. - constexpr static bool IsUnique() { return pop::IsUnique() && !pop::Has(C1); } - - /// Convert this StringType back to an std::string object (note: NOT constexpr) - static std::string ToString() { - std::stringstream ss; - ss << C1; - (ss << ... << Cs); - return ss.str(); - } - }; - - // Empty StringType template specialization - template <> - struct StringType<> { - static constexpr char FIRST = '\0'; ///< Empty string has null as "first" char - constexpr static int SIZE = 0; ///< Empty string as no length - - using this_t = StringType<>; ///< The type of the current StringType - // No pop_t; should give error if used on empty string - template using push = StringType; ///< Add char to front of string - template using push_back = StringType; ///< Add char to back of string - - /// Empty StringType does not contain the char C. - constexpr static bool Has(char C) { return false; } - - /// Empty StringType has 0 occurances of anything. - constexpr static int Count(int C) { return 0; } - - /// Empty StringType always returns ID -1 for failue to find. - constexpr static int GetID(int C) { return -1; } - - /// Empty StringType has size 0. - constexpr static int GetSize() { return 0; } - - /// Empty StringType is, in fact, empty. - constexpr static bool IsEmpty() { return true; } - - /// Empty StringType is always unique. - constexpr static bool IsUnique() { return true; } - }; - - constexpr size_t CalcStringSize(const char * in) { - size_t count = 0; - while (in[count] != 0) count++; - return count; - } - - - template - constexpr auto StringPacketToStringType(T packet) { - if constexpr (START >= CalcStringSize(packet())) return StringType<>(); - else { - using cur_t = typename decltype(StringPacketToStringType(packet))::template push; - return cur_t(); - } - } -} - -#endif diff --git a/include/emp/meta/TypeID.hpp b/include/emp/meta/TypeID.hpp index 9a565e93d2..71b4150106 100644 --- a/include/emp/meta/TypeID.hpp +++ b/include/emp/meta/TypeID.hpp @@ -1,5 +1,5 @@ // This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2016-2020. +// Copyright (C) Michigan State University, 2016-2021. // Released under the MIT Software license; see doc/LICENSE // // TypeID provides an easy way to convert types to strings. @@ -20,6 +20,8 @@ #include "../base/Ptr.hpp" #include "../base/vector.hpp" +#include "../datastructs/vector_utils.hpp" +#include "../tools/string_utils.hpp" #include "type_traits.hpp" #include "TypePack.hpp" @@ -54,6 +56,7 @@ namespace emp { virtual ~Info() { } virtual bool IsAbstract() const { return false; } + virtual bool IsArithmetic() const { return false; } virtual bool IsArray() const { return false; } virtual bool IsClass() const { return false; } virtual bool IsConst() const { return false; } @@ -62,13 +65,17 @@ namespace emp { virtual bool IsPointer() const { return false; } virtual bool IsReference() const { return false; } virtual bool IsTrivial() const { return false; } + virtual bool IsVoid() const { return false; } virtual bool IsVolatile() const { return false; } virtual bool IsTypePack() const { return false; } virtual size_t GetDecayID() const { return 0; } + virtual size_t GetElementID() const { return 0; } virtual size_t GetRemoveConstID() const { return 0; } virtual size_t GetRemoveCVID() const { return 0; } + virtual size_t GetRemoveExtentID() const { return 0; } + virtual size_t GetRemoveAllExtentsID() const { return 0; } virtual size_t GetRemovePtrID() const { return 0; } virtual size_t GetRemoveRefID() const { return 0; } virtual size_t GetRemoveVolatileID() const { return 0; } @@ -100,6 +107,7 @@ namespace emp { template struct InfoData : public Info { bool IsAbstract() const override { return std::is_abstract(); } + bool IsArithmetic() const override { return std::is_arithmetic(); } bool IsArray() const override { return std::is_array(); } bool IsClass() const override { return std::is_class(); } bool IsConst() const override { return std::is_const(); } @@ -108,6 +116,7 @@ namespace emp { bool IsPointer() const override { return emp::is_pointer(); } // Not std::is_pointer() to deal with emp::Ptr. bool IsReference() const override { return std::is_reference(); } bool IsTrivial() const override { return std::is_trivial(); } + bool IsVoid() const override { return std::is_same(); } bool IsVolatile() const override { return std::is_volatile(); } bool IsTypePack() const override { return emp::is_TypePack(); } @@ -117,6 +126,11 @@ namespace emp { if constexpr (std::is_same()) return (size_t) this; else return GetTypeID< decay_t >(); } + size_t GetElementID() const override { + using element_t = emp::element_t; + if constexpr (std::is_same()) return (size_t) this; + else return GetTypeID< element_t >(); + } size_t GetRemoveConstID() const override { using remove_const_t = std::remove_const_t; if constexpr (std::is_same()) return (size_t) this; @@ -127,6 +141,16 @@ namespace emp { if constexpr (std::is_same()) return (size_t) this; else return GetTypeID< remove_cv_t >(); } + size_t GetRemoveExtentID() const override { + using remove_extent_t = std::remove_extent_t; + if constexpr (std::is_same()) return (size_t) this; + else return GetTypeID< remove_extent_t >(); + } + size_t GetRemoveAllExtentsID() const override { + using remove_all_extents_t = std::remove_all_extents_t; + if constexpr (std::is_same()) return (size_t) this; + else return GetTypeID< remove_all_extents_t >(); + } size_t GetRemovePtrID() const override { using remove_ptr_t = emp::remove_pointer_t; if constexpr (std::is_same()) return (size_t) this; @@ -173,12 +197,12 @@ namespace emp { } // If this variable is a string or can be directly converted to a string, do so. - if constexpr (std::is_convertible::value) { + else if constexpr (std::is_convertible::value) { return (std::string) *ptr.ReinterpretCast(); } // If this variable is a char, treat it as a single-character string. - if constexpr (std::is_same::value) { + else if constexpr (std::is_same::value) { return std::string(1, (char) *ptr.ReinterpretCast()); } @@ -187,8 +211,12 @@ namespace emp { return std::to_string( *ptr.ReinterpretCast() ); } + else if constexpr (emp::is_emp_vector::value) { + return emp::ToString( *ptr.ReinterpretCast() ); + } + // If we made it this far, we don't know how to convert... - return ""; + return "[N/A]"; } bool FromDouble(double value, const emp::Ptr ptr) const override { @@ -202,7 +230,7 @@ namespace emp { // If this type is convertable to a double, cast the pointer to the correct type, de-reference it, // and then return the conversion. Otherwise return NaN if constexpr (std::is_convertible::value) { - *ptr.ReinterpretCast() = value; + *ptr.ReinterpretCast() = (base_t) value; return true; } @@ -232,7 +260,7 @@ namespace emp { // If this variable is a numeric value, use from_string. else if constexpr (std::is_arithmetic::value) { - *ptr.ReinterpretCast() = stod(value); + *ptr.ReinterpretCast() = (base_t) stod(value); return true; } @@ -258,6 +286,10 @@ namespace emp { operator bool() const noexcept { return info_ptr->init; } bool operator==(TypeID in) const { return info_ptr == in.info_ptr; } bool operator!=(TypeID in) const { return info_ptr != in.info_ptr; } + bool operator< (TypeID in) const { return info_ptr < in.info_ptr; } + bool operator<=(TypeID in) const { return info_ptr <=in.info_ptr; } + bool operator> (TypeID in) const { return info_ptr > in.info_ptr; } + bool operator>=(TypeID in) const { return info_ptr >= in.info_ptr; } /// Get a unique numerical ID for this TypeID object. size_t GetID() const { return (size_t) info_ptr.Raw(); } @@ -268,25 +300,39 @@ namespace emp { /// Update the name for ALL instances of this TypeID. void SetName(const std::string & in_name) { emp_assert(info_ptr); info_ptr->name = in_name; } - bool IsInitialized() const { return info_ptr->init ; } + bool IsInitialized() const { return info_ptr->init; } void SetInitialized(bool _in=true) { info_ptr->init = _in; } bool IsAbstract() const { return info_ptr->IsAbstract(); } - bool IsArray() const { return info_ptr->IsArray() ; } - bool IsClass() const { return info_ptr->IsClass() ; } - bool IsConst() const { return info_ptr->IsConst() ; } - bool IsEmpty() const { return info_ptr->IsEmpty() ; } - bool IsObject() const { return info_ptr->IsObject() ; } - bool IsPointer() const { return info_ptr->IsPointer() ; } - bool IsReference() const { return info_ptr->IsReference() ; } - bool IsTrivial() const { return info_ptr->IsTrivial() ; } - bool IsVolatile() const { return info_ptr->IsVolatile() ; } - - bool IsTypePack() const { return info_ptr->IsTypePack() ; } - + bool IsArithmetic() const { return info_ptr->IsArithmetic(); } + bool IsArray() const { return info_ptr->IsArray(); } + bool IsClass() const { return info_ptr->IsClass(); } + bool IsConst() const { return info_ptr->IsConst(); } + bool IsEmpty() const { return info_ptr->IsEmpty(); } + bool IsObject() const { return info_ptr->IsObject(); } + bool IsPointer() const { return info_ptr->IsPointer(); } + bool IsReference() const { return info_ptr->IsReference(); } + bool IsTrivial() const { return info_ptr->IsTrivial(); } + bool IsVoid() const { return info_ptr->IsVoid(); } + bool IsVolatile() const { return info_ptr->IsVolatile(); } + + bool IsTypePack() const { return info_ptr->IsTypePack(); } + + template bool IsType() const { return *this == GetTypeID(); } + + template + bool IsTypeIn() const { + if (IsType()) return true; + if constexpr (sizeof...(Ts) > 0) return IsTypeIn(); + else return false; + } + TypeID GetDecayTypeID() const { return info_ptr->GetDecayID(); } + TypeID GetElementTypeID() const { return info_ptr->GetElementID(); } TypeID GetRemoveConstTypeID() const { return info_ptr->GetRemoveConstID(); } TypeID GetRemoveCVTypeID() const { return info_ptr->GetRemoveCVID(); } + TypeID GetRemoveExtentTypeID() const { return info_ptr->GetRemoveExtentID(); } + TypeID GetRemoveAllExtentsTypeID() const { return info_ptr->GetRemoveAllExtentsID(); } TypeID GetRemovePointerTypeID() const { return info_ptr->GetRemovePtrID(); } TypeID GetRemoveReferenceTypeID() const { return info_ptr->GetRemoveRefID(); } TypeID GetRemoveVolatileTypeID() const { return info_ptr->GetRemoveVolatileID(); } @@ -348,18 +394,30 @@ namespace emp { info.name = typeid(T).name(); // Now, fix the name if we can be more precise about it. - if (info.IsConst()) { + if constexpr (std::is_const()) { info.name = "const "s + type_id.GetRemoveConstTypeID().GetName(); } - else if (info.IsVolatile()) { + else if constexpr (std::is_volatile()) { info.name = "volatile "s + type_id.GetRemoveVolatileTypeID().GetName(); } - else if (info.IsPointer()) { + else if constexpr (std::is_array()) { + info.name = type_id.GetRemoveAllExtentsTypeID().GetName(); + if constexpr (std::rank::value > 0) info.name += "[" + emp::to_string(std::extent::value) + "]"; + if constexpr (std::rank::value > 1) info.name += "[" + emp::to_string(std::extent::value) + "]"; + if constexpr (std::rank::value > 2) info.name += "[" + emp::to_string(std::extent::value) + "]"; + if constexpr (std::rank::value > 3) info.name += "[" + emp::to_string(std::extent::value) + "]"; + if constexpr (std::rank::value > 4) info.name += "[" + emp::to_string(std::extent::value) + "]"; + if constexpr (std::rank::value > 5) info.name += "[...]"; + } + else if constexpr (emp::is_pointer()) { info.name = type_id.GetRemovePointerTypeID().GetName() + '*'; } - else if (info.IsReference()) { + else if constexpr (std::is_reference()) { info.name = type_id.GetRemoveReferenceTypeID().GetName() + '&'; } + else if constexpr (emp::is_emp_vector()) { + info.name = "vector<"s + type_id.GetElementTypeID().GetName() + '>'; + } else if constexpr (emp::is_TypePack()) { emp::vector ids = GetTypePackIDs(); info.name = "TypePack<"; @@ -406,7 +464,7 @@ namespace emp { namespace std { - /// Hash function to allow BitVector to be used with maps and sets (must be in std). + /// Hash function to allow TypeID to be used with maps and sets (must be in std). template <> struct hash { std::size_t operator()(const emp::TypeID & id) const { diff --git a/include/emp/meta/TypePack.hpp b/include/emp/meta/TypePack.hpp index 91828c8d9d..53bb658005 100644 --- a/include/emp/meta/TypePack.hpp +++ b/include/emp/meta/TypePack.hpp @@ -44,6 +44,7 @@ * resize - Resize pack to N types; if N greater than current size, pad with D. * merge

- Append all of pack P to the end of this pack. * find_union

- Join this pack to P, keeping only one of each type. + * find_intersect

- Limit to only common types between this pack and P * reverse - Reverse the order of types in this pack. * rotate - Move the first type in pack to the end. * @@ -56,6 +57,8 @@ * a FILTER::value == false. * find - Convert to first type, T, that can legally form FILTER and does not * have a FILTER::value == false. + * remove_t - Remove type T from anywhere in the pack. + * make_unique - Remove all type duplications. * wrap - Convert to TypePack where all members are run through WRAPPER * * @@ -206,7 +209,10 @@ namespace emp { template struct TypePack { /// Return a bool indicating whether the specified type is present. - template constexpr static bool Has() { return has_type(); } + template constexpr static bool Has() { return emp::has_type(); } + + /// Return a type indicating whether the specified type is present. + template using has_type = typename std::integral_constant()>; /// Count the number of instances of the specified type. template constexpr static size_t Count() { return count_type(); } @@ -279,6 +285,9 @@ namespace emp { /// Join this TypePack with another, keeping only one of each type. template using find_union = typename internal::tp_shift::type1::make_unique; + /// Limit to only common types between this TypePack and another + template using find_intersect = typename internal::tp_filter_t; + /// Rearrange types in TypePack into reverse order. using reverse = typename pop::reverse::template push_back; diff --git a/include/emp/meta/ValPack.hpp b/include/emp/meta/ValPack.hpp index 3e10561fc9..ee176b0e19 100644 --- a/include/emp/meta/ValPack.hpp +++ b/include/emp/meta/ValPack.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Empirical, https://github.com/devosoft/Empirical * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2016-2018 + * @date 2016-2021. * * @file ValPack.hpp * @brief A set of values that can be manipulated at compile time (good for metaprogramming) @@ -253,7 +253,7 @@ namespace emp { static std::string ToString() { return ""; } - static void PrintVals(std::ostream & os=std::cout) { ; } + static void PrintVals(std::ostream & /* os */=std::cout) { ; } }; namespace pack { diff --git a/include/emp/base/macro_math.hpp b/include/emp/meta/macro_math.hpp similarity index 100% rename from include/emp/base/macro_math.hpp rename to include/emp/meta/macro_math.hpp diff --git a/include/emp/base/macros.hpp b/include/emp/meta/macros.hpp similarity index 99% rename from include/emp/base/macros.hpp rename to include/emp/meta/macros.hpp index 78228f99a6..f225e352b1 100644 --- a/include/emp/base/macros.hpp +++ b/include/emp/meta/macros.hpp @@ -363,7 +363,7 @@ EMP_EVAL_G( EMP_WRAP_EACH_1ARG_256 EMP_EMPTY() (P, EMP_POP_ARGS_256(__VA_ARGS__)) ) /// @endcond -/// imilar to EMP_WRAP_ARGS, but puts a COMMA between each arg pair. +/// Similar to EMP_WRAP_EACH, but puts a COMMA between each arg pair. #define EMP_WRAP_ARGS(W, ...) EMP_POP_ARGS_1( ~ EMP_CALL_BY_PACKS(EMP_WRAP_ARGS_, W, __VA_ARGS__) ) /// @cond MACROS #define EMP_WRAP_ARGS_1(W, A, ...) , W(A) diff --git a/include/emp/meta/meta.hpp b/include/emp/meta/meta.hpp index e657a071cb..8012d87264 100644 --- a/include/emp/meta/meta.hpp +++ b/include/emp/meta/meta.hpp @@ -1,5 +1,5 @@ // This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2016-2018 +// Copyright (C) Michigan State University, 2016-2021. // Released under the MIT Software license; see doc/LICENSE // // A bunch of C++ Template Meta-programming tricks. @@ -17,22 +17,63 @@ #include #include +#include "../base/vector.hpp" + namespace emp { - // A function that will take any number of argument and do nothing with them. + /// A function that will take any number of argument and do nothing with them. template void DoNothing(Ts...) { ; } - // Effectively create a function (via constructor) where all args are computed, then ignored. + /// Effectively create a function (via constructor) where all args are computed, then ignored. struct run_and_ignore { template run_and_ignore(T&&...) {} }; - // Trim off a specific type position from a pack. + /// Trim off a specific type position from a pack. template using first_type = T1; template using second_type = T2; template using third_type = T3; - // Create a placeholder template to substitute for a real type. + /// Create a placeholder template to substitute for a real type. template struct PlaceholderType; + /// Group types in a parameter pack to build a vector of a designated type. + template void BuildObjVector1(emp::vector &) { } + template void BuildObjVector2(emp::vector &) { } + template void BuildObjVector3(emp::vector &) { } + template void BuildObjVector4(emp::vector &) { } + + template + void BuildObjVector1(emp::vector& v, T1& arg1, Ts&... extras) + { v.emplace_back( arg1 ); BuildObjVector1(v, extras...); } + + template + void BuildObjVector2(emp::vector& v, T1& arg1, T2& arg2, Ts&... extras) + { v.emplace_back( arg1, arg2 ); BuildObjVector2(v, extras...); } + + template + void BuildObjVector3(emp::vector& v, T1& arg1, T2& arg2, T3& arg3, Ts&... extras) + { v.emplace_back( arg1, arg2, arg3 ); BuildObjVector3(v, extras...); } + + template + void BuildObjVector4(emp::vector& v, T1& arg1, T2& arg2, T3& arg3, T4& arg4, Ts&... extras) + { v.emplace_back( arg1, arg2, arg3, arg4 ); BuildObjVector4(v, extras...); } + + template + emp::vector BuildObjVector(Ts &... args) { + emp::vector out_v; + constexpr size_t TOTAL_ARGS = sizeof...(Ts); + static_assert((TOTAL_ARGS % NUM_ARGS) == 0, + "emp::BuildObjVector() : Must have the same number of args for each object."); + out_v.reserve(TOTAL_ARGS / NUM_ARGS); + + if constexpr (NUM_ARGS == 1) BuildObjVector1(out_v, args...); + else if constexpr (NUM_ARGS == 2) BuildObjVector2(out_v, args...); + else if constexpr (NUM_ARGS == 3) BuildObjVector3(out_v, args...); + else if constexpr (NUM_ARGS == 4) BuildObjVector4(out_v, args...); + static_assert(NUM_ARGS < 5, "BuildObjVector currently has a cap of 4 arguments per object."); + + return out_v; + } + // Index into a template parameter pack to grab a specific type. #ifndef DOXYGEN_SHOULD_SKIP_THIS namespace internal { @@ -127,7 +168,7 @@ namespace emp { #ifndef DOXYGEN_SHOULD_SKIP_THIS namespace internal { template