From 24f08043850b08eeda629f3f8fb5e76391fc2449 Mon Sep 17 00:00:00 2001 From: tokusanya Date: Mon, 5 Aug 2024 13:58:23 -0600 Subject: [PATCH] Dynamic topology phase 2 (#476) * IOSS: Add broker class to dynamic topology The original philosophy of one observer per region is still maintained with the observers that represent the same FEM model being grouped together. This means that topology changes on one observer is propagated to others in the same group * Add missing serialization calls. * Fix thread-safety issues --------- Co-authored-by: Greg Sjaardema --- .../libraries/ioss/src/Ioss_DatabaseIO.h | 14 + .../libraries/ioss/src/Ioss_DynamicTopology.C | 1001 +++++++----- .../libraries/ioss/src/Ioss_DynamicTopology.h | 146 +- .../seacas/libraries/ioss/src/Ioss_Region.C | 33 +- .../seacas/libraries/ioss/src/Ioss_Region.h | 4 + .../ioss/src/exodus/Ioex_BaseDatabaseIO.C | 49 + .../ioss/src/exodus/Ioex_BaseDatabaseIO.h | 2 + .../ioss/src/exodus/Ioex_DatabaseIO.C | 3 + .../ioss/src/init/Ionit_Initializer.C | 3 + .../src/unit_tests/UnitTestDynamicTopology.C | 1358 +++++++++++------ 10 files changed, 1724 insertions(+), 889 deletions(-) diff --git a/packages/seacas/libraries/ioss/src/Ioss_DatabaseIO.h b/packages/seacas/libraries/ioss/src/Ioss_DatabaseIO.h index 3ef175acba..44db7cfe07 100644 --- a/packages/seacas/libraries/ioss/src/Ioss_DatabaseIO.h +++ b/packages/seacas/libraries/ioss/src/Ioss_DatabaseIO.h @@ -285,6 +285,18 @@ namespace Ioss { return open_root_group_nl(); } + int num_child_group() + { + IOSS_FUNC_ENTER(m_); + return num_child_group_nl(); + } + + bool open_child_group(int index) + { + IOSS_FUNC_ENTER(m_); + return open_child_group_nl(index); + } + Ioss::NameList groups_describe(bool return_full_names = false) { IOSS_FUNC_ENTER(m_); @@ -778,6 +790,8 @@ namespace Ioss { return elemMap.global_to_local(global); } + virtual int num_child_group_nl() { return 0; } + virtual bool open_child_group_nl(int /* index */) { return false; } virtual bool open_root_group_nl() { return false; } virtual bool open_group_nl(const std::string & /* group_name */) { return false; } virtual bool create_subgroup_nl(const std::string & /* group_name */) { return false; } diff --git a/packages/seacas/libraries/ioss/src/Ioss_DynamicTopology.C b/packages/seacas/libraries/ioss/src/Ioss_DynamicTopology.C index 4bb8390e3f..639f6ba47a 100644 --- a/packages/seacas/libraries/ioss/src/Ioss_DynamicTopology.C +++ b/packages/seacas/libraries/ioss/src/Ioss_DynamicTopology.C @@ -37,491 +37,692 @@ #include #include -#include -#include #include #include +#include +#include #include "Ioss_ParallelUtils.h" namespace Ioss { - void DynamicTopologyObserver::check_region() const - { - if (nullptr == m_region) { - std::ostringstream errmsg; - fmt::print(errmsg, "ERROR: A region has not been registered with the " - "Dynamic Topology Observer.\n\n"); - IOSS_ERROR(errmsg); - } +void DynamicTopologyObserver::check_region() const +{ + if(nullptr == m_region) { + std::ostringstream errmsg; + fmt::print(errmsg, + "ERROR: A region has not been registered with the " + "Dynamic Topology Observer.\n\n"); + IOSS_ERROR(errmsg); + } +} + +void DynamicTopologyObserver::register_region(Region *region) +{ + if(nullptr != region && nullptr != m_region && region != m_region) { + std::ostringstream errmsg; + fmt::print(errmsg, + "ERROR: Attempt to re-register different region on " + "Dynamic Topology Observer.\n\n"); + IOSS_ERROR(errmsg); } - void DynamicTopologyObserver::register_region(Region *region) - { - if (nullptr != m_region && region != m_region) { - std::ostringstream errmsg; - fmt::print(errmsg, "ERROR: Attempt to re-register different region on " - "Dynamic Topology Observer.\n\n"); - IOSS_ERROR(errmsg); - } - - m_region = region; + m_region = region; +} + +void DynamicTopologyObserver::register_notifier(DynamicTopologyNotifier *notifier) +{ + if(nullptr != notifier && nullptr != m_notifier && notifier != m_notifier) { + std::ostringstream errmsg; + fmt::print(errmsg, + "ERROR: Attempt to re-register different notifier on " + "Dynamic Topology Observer.\n\n"); + IOSS_ERROR(errmsg); } - void DynamicTopologyObserver::set_cumulative_topology_modification(unsigned int type) - { - m_cumulativeTopologyModification = type; + m_notifier = notifier; +} + +void DynamicTopologyObserver::set_cumulative_topology_modification(unsigned int type) +{ m_cumulativeTopologyModification = type; } + +unsigned int DynamicTopologyObserver::get_cumulative_topology_modification() const +{ return m_cumulativeTopologyModification; } + +unsigned int DynamicTopologyObserver::get_topology_modification() const +{ return m_topologyModification; } + +void DynamicTopologyObserver::set_topology_modification_nl(unsigned int type) +{ + m_topologyModification |= type; + m_cumulativeTopologyModification |= type; +} + +void DynamicTopologyObserver::set_topology_modification(unsigned int type) +{ + if(!(m_topologyModification & type)) { + set_topology_modification_nl(type); + + if(nullptr != m_notifier) { + for(auto observer : m_notifier->get_observers()) { + observer->set_topology_modification_nl(type); + } + } } +} - unsigned int DynamicTopologyObserver::get_cumulative_topology_modification() const - { - return m_cumulativeTopologyModification; +void DynamicTopologyObserver::reset_topology_modification() +{ + m_topologyModification = TOPOLOGY_SAME; +} + +void DynamicTopologyObserver::reset_topology_modification_all() +{ + if(m_topologyModification != TOPOLOGY_SAME) { + reset_topology_modification(); + + if(nullptr != m_notifier) { + for(auto observer : m_notifier->get_observers()) { + observer->reset_topology_modification(); + } + } + } +} + +bool DynamicTopologyObserver::is_topology_modified() const +{ return m_topologyModification != TOPOLOGY_SAME; } + +const ParallelUtils &DynamicTopologyObserver::util() const +{ + check_region(); + return m_region->get_database()->util(); +} + +void DynamicTopologyObserver::synchronize_topology_modified_flags() +{ + check_region(); + int num_processors = m_region->get_database()->parallel_size(); + // Synchronize the topology flags between all processors in case + // it has not been set consistently. + if (num_processors > 1) { + static unsigned int buffer[2]; + buffer[0] = m_cumulativeTopologyModification; + buffer[1] = m_topologyModification; + + util().attribute_reduction(2*sizeof(unsigned int), reinterpret_cast(buffer)); + + m_cumulativeTopologyModification = buffer[0]; + m_topologyModification = buffer[1]; + } +} + +int DynamicTopologyObserver::get_cumulative_topology_modification_field() +{ + check_region(); + const std::string variable_name = topology_modification_change_name(); + + int ivalue = 0; + + if (m_region->field_exists(variable_name)) { + Field topo_field = m_region->get_field(variable_name); + if (topo_field.get_type() == Field::INTEGER) { + m_region->get_field_data(variable_name, &ivalue, sizeof(int)); + } else { + double value; + m_region->get_field_data(variable_name, &value, sizeof(double)); + ivalue = (int)value; + } } - unsigned int DynamicTopologyObserver::get_topology_modification() const - { - return m_topologyModification; + int num_processors = m_region->get_database()->parallel_size(); + // Synchronize the value between all processors in case + // it has not been set consistently. + if (num_processors > 1) { + unsigned int buffer[1]; + buffer[0] = ivalue; + + util().attribute_reduction(sizeof(unsigned int), reinterpret_cast(buffer)); + + ivalue = (int)buffer[0]; } - void DynamicTopologyObserver::set_topology_modification(unsigned int type) - { - m_topologyModification |= type; - m_cumulativeTopologyModification |= type; + m_cumulativeTopologyModification = ivalue; + + return ivalue; +} + +void DynamicTopologyObserver::define_model() +{ + +} + +void DynamicTopologyObserver::write_model() +{ + +} + +void DynamicTopologyObserver::define_transient() +{ + +} + + +DynamicTopologyBroker* DynamicTopologyBroker::broker() +{ + static DynamicTopologyBroker broker_; + return &broker_; +} + +void DynamicTopologyBroker::register_model(const std::string& model_name) +{ + auto iter = m_notifiers.find(model_name); + if(iter != m_notifiers.end()) { + return; } - void DynamicTopologyObserver::reset_topology_modification() - { - m_topologyModification = TOPOLOGY_SAME; + m_notifiers[model_name] = std::make_shared(model_name); +} + +std::shared_ptr DynamicTopologyBroker::get_notifier(const std::string& model_name) const +{ + auto iter = m_notifiers.find(model_name); + if(iter != m_notifiers.end()) { + return iter->second; } - bool DynamicTopologyObserver::is_topology_modified() const { return m_topologyModification != 0; } + return {}; +} - const ParallelUtils &DynamicTopologyObserver::util() const - { - check_region(); - return m_region->get_database()->util(); +std::vector> DynamicTopologyBroker::get_observers(const std::string& model_name) const +{ + std::vector> observers; + + auto notifier = get_notifier(model_name); + + if(notifier) { + return notifier->get_observers(); } - void DynamicTopologyObserver::synchronize_topology_modified_flags() - { - check_region(); - int num_processors = m_region->get_database()->parallel_size(); - // Synchronize the topology flags between all processors in case - // it has not been set consistently. - if (num_processors > 1) { - static unsigned int buffer[2]; - buffer[0] = m_cumulativeTopologyModification; - buffer[1] = m_topologyModification; - - util().attribute_reduction(2 * sizeof(unsigned int), reinterpret_cast(buffer)); - - m_cumulativeTopologyModification = buffer[0]; - m_topologyModification = buffer[1]; - } + return observers; +} + +void DynamicTopologyBroker::remove_model(const std::string& model_name) +{ + auto iter = m_notifiers.find(model_name); + if(iter != m_notifiers.end()) { + m_notifiers.erase(iter); } +} - int DynamicTopologyObserver::get_cumulative_topology_modification_field() - { - check_region(); - const std::string variable_name = topology_modification_change_name(); +void DynamicTopologyBroker::clear_models() +{ + m_notifiers.clear(); +} - int ivalue = 0; +void DynamicTopologyBroker::register_observer(const std::string& model_name, + std::shared_ptr observer) +{ + auto notifier = get_notifier(model_name); - if (m_region->field_exists(variable_name)) { - Field topo_field = m_region->get_field(variable_name); - if (topo_field.get_type() == Field::INTEGER) { - m_region->get_field_data(variable_name, &ivalue, sizeof(int)); - } - else { - double value; - m_region->get_field_data(variable_name, &value, sizeof(double)); - ivalue = (int)value; - } - } + if(!notifier) { + register_model(model_name); + notifier = get_notifier(model_name); + } - int num_processors = m_region->get_database()->parallel_size(); - // Synchronize the value between all processors in case - // it has not been set consistently. - if (num_processors > 1) { - unsigned int buffer[1]; - buffer[0] = ivalue; + notifier->register_observer(observer); +} - util().attribute_reduction(sizeof(unsigned int), reinterpret_cast(buffer)); +void DynamicTopologyBroker::register_observer(const std::string& model_name, + std::shared_ptr observer, + Region& region) +{ + region.register_mesh_modification_observer(observer); + register_observer(model_name, observer); +} - ivalue = (int)buffer[0]; - } +void DynamicTopologyBroker::reset_topology_modification(const std::string& model_name) +{ + auto notifier = get_notifier(model_name); - m_cumulativeTopologyModification = ivalue; + if(!notifier) return; - return ivalue; - } + notifier->reset_topology_modification(); +} - void DynamicTopologyObserver::define_model(IOSS_MAYBE_UNUSED Region ®ion) {} +void DynamicTopologyBroker::set_topology_modification(const std::string& model_name, unsigned int type) +{ + auto notifier = get_notifier(model_name); - void DynamicTopologyObserver::write_model(IOSS_MAYBE_UNUSED Region ®ion) {} + if(!notifier) return; - void DynamicTopologyObserver::define_transient(IOSS_MAYBE_UNUSED Region ®ion) {} + notifier->set_topology_modification(type); +} - DynamicTopologyFileControl::DynamicTopologyFileControl(Region *region, - unsigned int fileCyclicCount, - IfDatabaseExistsBehavior &ifDatabaseExists, - unsigned int &dbChangeCount) - : m_region(region), m_fileCyclicCount(fileCyclicCount), m_ifDatabaseExists(ifDatabaseExists), - m_dbChangeCount(dbChangeCount) - { - if (nullptr == region) { - std::ostringstream errmsg; - fmt::print(errmsg, "ERROR: null region passed in as argument to DynamicTopologyFileControl"); - IOSS_ERROR(errmsg); - } - m_ioDB = region->get_property("base_filename").get_string(); - m_dbType = region->get_property("database_type").get_string(); +struct DynamicTopologyObserverCompare { + bool operator()(const std::shared_ptr & lhs, + const std::shared_ptr & rhs) const { + assert(lhs && (lhs->get_region() != nullptr)); + assert(rhs && (rhs->get_region() != nullptr)); + return (lhs->get_region() < rhs->get_region()); + } +}; + +void DynamicTopologyNotifier::register_observer(std::shared_ptr observer) +{ + observer->register_notifier(this); + m_observers.push_back(observer); + std::sort(m_observers.begin(), m_observers.end(), DynamicTopologyObserverCompare()); +} + +void DynamicTopologyNotifier::unregister_observer(std::shared_ptr observer) +{ + auto iter = std::find(m_observers.begin(), m_observers.end(), observer); + if (iter != m_observers.end()) { + (*iter)->register_notifier(nullptr); + m_observers.erase(iter); } +} - const ParallelUtils &DynamicTopologyFileControl::util() const - { - return m_region->get_database()->util(); +void DynamicTopologyNotifier::reset_topology_modification() +{ + for(std::shared_ptr& observer : m_observers) { + observer->reset_topology_modification(); } +} - bool DynamicTopologyFileControl::file_exists(const std::string &filename, - const std::string &db_type, - Ioss::DatabaseUsage db_usage) - { - bool exists = false; - int par_size = m_region->get_database()->parallel_size(); - int par_rank = m_region->get_database()->parallel_rank(); - bool is_parallel = par_size > 1; - std::string full_filename = filename; - if (is_parallel && db_type == "exodusII" && db_usage != Ioss::WRITE_HISTORY) { - full_filename = Ioss::Utils::decode_filename(filename, par_rank, par_size); - } +void DynamicTopologyNotifier::set_topology_modification(unsigned int type) +{ + for(std::shared_ptr& observer : m_observers) { + observer->set_topology_modification(type); + } +} + + +DynamicTopologyFileControl::DynamicTopologyFileControl(Region *region, unsigned int fileCyclicCount, + IfDatabaseExistsBehavior &ifDatabaseExists, + unsigned int &dbChangeCount) + : m_region(region) + , m_fileCyclicCount(fileCyclicCount) + , m_ifDatabaseExists(ifDatabaseExists) + , m_dbChangeCount(dbChangeCount) +{ + if(nullptr == region) { + std::ostringstream errmsg; + fmt::print(errmsg, "ERROR: null region passed in as argument to DynamicTopologyFileControl"); + IOSS_ERROR(errmsg); + } + + m_ioDB = region->get_property("base_filename").get_string(); + m_dbType = region->get_property("database_type").get_string(); +} + +const ParallelUtils &DynamicTopologyFileControl::util() const +{ + return m_region->get_database()->util(); +} + +bool DynamicTopologyFileControl::file_exists(const std::string &filename, + const std::string &db_type, + Ioss::DatabaseUsage db_usage) +{ + bool exists = false; + int par_size = m_region->get_database()->parallel_size(); + int par_rank = m_region->get_database()->parallel_rank(); + bool is_parallel = par_size > 1; + std::string full_filename = filename; + if (is_parallel && db_type == "exodusII" && db_usage != Ioss::WRITE_HISTORY) { + full_filename = Ioss::Utils::decode_filename(filename, par_rank, par_size); + } - if (!is_parallel || par_rank == 0) { - // Now, see if this file exists... - // Don't want to do a system call on all processors since it can take minutes - // on some of the larger machines, filesystems, and processor counts... - Ioss::FileInfo file = Ioss::FileInfo(full_filename); - exists = file.exists(); + if (!is_parallel || par_rank == 0) { + // Now, see if this file exists... + // Don't want to do a system call on all processors since it can take minutes + // on some of the larger machines, filesystems, and processor counts... + Ioss::FileInfo file = Ioss::FileInfo(full_filename); + exists = file.exists(); + } + + if (is_parallel) { + int iexists = exists ? 1 : 0; + util().broadcast(iexists, 0); + exists = iexists == 1; + } + return exists; +} + +std::string DynamicTopologyFileControl::get_unique_filename(Ioss::DatabaseUsage db_usage) +{ + std::string filename = m_ioDB; + + do { + // Run this loop at least once for all files. If this is an automatic + // restart, then make sure that the generated file does not already exist, + // so keep running the loop until we generate a filename that doesn't exist... + std::ostringstream tmp_filename; + tmp_filename << m_ioDB; + + // Don't append the "-s000X" the first time in case the base filename doesn't + // exist -- we want write to the name specified by the user if at all possible and + // once that exists, then start adding on the suffix... + if (m_dbChangeCount > 1) { + tmp_filename << "-s" << std::setw(4) << std::setfill('0') << m_dbChangeCount; } + filename = tmp_filename.str(); + ++m_dbChangeCount; + } while(file_exists(filename, m_dbType, db_usage)); + --m_dbChangeCount; + return filename; +} + +std::string DynamicTopologyFileControl::construct_database_filename(int& step, Ioss::DatabaseUsage db_usage) +{ + // Filename will be of the form -- ioDB-sxxxx where xxxx is step + // number. Assume maximum of 9999 steps (will do more, but won't have + // good lineup of step numbers. + // Check database for validity (filename and a type) + if(m_ioDB.empty() || m_dbType.empty()) + { + std::string error_message; + if(m_dbType.empty()) + error_message += "The database TYPE has not been defined\n"; - if (is_parallel) { - int iexists = exists ? 1 : 0; - util().broadcast(iexists, 0); - exists = iexists == 1; + if(m_ioDB.empty()) + { + error_message += "The database FILENAME has not been defined\n"; } - return exists; + std::ostringstream errmsg; + fmt::print(errmsg, error_message); + IOSS_ERROR(errmsg); } - - std::string DynamicTopologyFileControl::get_unique_filename(Ioss::DatabaseUsage db_usage) + assert(!m_ioDB.empty()); + assert(!m_dbType.empty()); + std::string filename = m_ioDB; + if(m_fileCyclicCount > 0) { - std::string filename = m_ioDB; - - do { - // Run this loop at least once for all files. If this is an automatic - // restart, then make sure that the generated file does not already exist, - // so keep running the loop until we generate a filename that doesn't exist... - std::ostringstream tmp_filename; - tmp_filename << m_ioDB; - - // Don't append the "-s000X" the first time in case the base filename doesn't - // exist -- we want write to the name specified by the user if at all possible and - // once that exists, then start adding on the suffix... - if (m_dbChangeCount > 1) { - tmp_filename << "-s" << std::setw(4) << std::setfill('0') << m_dbChangeCount; - } - filename = tmp_filename.str(); - ++m_dbChangeCount; - } while (file_exists(filename, m_dbType, db_usage)); - --m_dbChangeCount; - return filename; + // In this mode, we close the old file and open a new file + // every time this is called. The file suffix cycles through + // the first fileCyclicCount'th entries in A,B,C,D,E,F,... + if(step == 0) + step++; + + static std::string suffix = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::string tmp = "-" + suffix.substr((step - 1) % m_fileCyclicCount, 1); + filename += tmp; + m_properties.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_OVERWRITE)); } - - std::string DynamicTopologyFileControl::construct_database_filename(int &step, - Ioss::DatabaseUsage db_usage) + else { - // Filename will be of the form -- ioDB-sxxxx where xxxx is step - // number. Assume maximum of 9999 steps (will do more, but won't have - // good lineup of step numbers. - // Check database for validity (filename and a type) - if (m_ioDB.empty() || m_dbType.empty()) { - std::string error_message; - if (m_dbType.empty()) - error_message += "The database TYPE has not been defined\n"; - - if (m_ioDB.empty()) { - error_message += "The database FILENAME has not been defined\n"; - } - std::ostringstream errmsg; - fmt::print(errmsg, error_message); - IOSS_ERROR(errmsg); + if(m_region->model_is_written()) + { + // After the initial open, we want to add suffix if the topology changes + // during the run + m_ifDatabaseExists = Ioss::DB_ADD_SUFFIX_OVERWRITE; } - assert(!m_ioDB.empty()); - assert(!m_dbType.empty()); - std::string filename = m_ioDB; - if (m_fileCyclicCount > 0) { - // In this mode, we close the old file and open a new file - // every time this is called. The file suffix cycles through - // the first fileCyclicCount'th entries in A,B,C,D,E,F,... - if (step == 0) - step++; - - static std::string suffix = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - std::string tmp = "-" + suffix.substr((step - 1) % m_fileCyclicCount, 1); - filename += tmp; - m_properties.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_OVERWRITE)); - } - else { - if (m_region->model_is_written()) { - // After the initial open, we want to add suffix if the topology changes - // during the run - m_ifDatabaseExists = Ioss::DB_ADD_SUFFIX_OVERWRITE; - } - // Handle complications of DB_APPEND mode... - // If in DB_APPEND mode, then we don't output metadata - // information, so some knowledge is needed at this level if - // we are appending. If user specified APPEND, but the file - // doesn't yet exist OR it does exist and we are not - // restarting, change the mode to OVERWRITE. - // 0. Must be restarting; either manual or automatic. - std::shared_ptr observer = - m_region->get_mesh_modification_observer(); - - if (m_ifDatabaseExists == Ioss::DB_APPEND) { - if (!observer->is_restart_requested()) { - // Not restarting - m_ifDatabaseExists = Ioss::DB_OVERWRITE; + // Handle complications of DB_APPEND mode... + // If in DB_APPEND mode, then we don't output metadata + // information, so some knowledge is needed at this level if + // we are appending. If user specified APPEND, but the file + // doesn't yet exist OR it does exist and we are not + // restarting, change the mode to OVERWRITE. + // 0. Must be restarting; either manual or automatic. + std::shared_ptr observer = m_region->get_mesh_modification_observer(); + + if(m_ifDatabaseExists == Ioss::DB_APPEND) + { + if(!observer->is_restart_requested()) + { + // Not restarting + m_ifDatabaseExists = Ioss::DB_OVERWRITE; + } + else if(!file_exists(m_ioDB, m_dbType, db_usage)) + { + m_ifDatabaseExists = Ioss::DB_OVERWRITE; + } + } + if(step > 1 || (m_dbChangeCount > 1)) + { + // Use the !is_input_event test since restart input files already have the + // -s000x extension... + if(m_ifDatabaseExists == Ioss::DB_APPEND) + { + std::ostringstream tmp_filename; + tmp_filename << m_ioDB; + filename = m_ioDB; + if(m_dbChangeCount > 1) + { + tmp_filename << "-s" << std::setw(4) << std::setfill('0') << m_dbChangeCount; + } + size_t inc = 0; + while(file_exists(tmp_filename.str(), m_dbType, db_usage)) + { + filename = tmp_filename.str(); + tmp_filename.clear(); + tmp_filename.str(""); + tmp_filename << m_ioDB << "-s" << std::setw(4) << std::setfill('0') << m_dbChangeCount + (++inc); } - else if (!file_exists(m_ioDB, m_dbType, db_usage)) { + if(inc > 0) + { + m_dbChangeCount += (inc - 1); + } + else + { m_ifDatabaseExists = Ioss::DB_OVERWRITE; } } - if (step > 1 || (m_dbChangeCount > 1)) { - // Use the !is_input_event test since restart input files already have the - // -s000x extension... - if (m_ifDatabaseExists == Ioss::DB_APPEND) { + else if(m_ifDatabaseExists == Ioss::DB_ADD_SUFFIX) + { + filename = get_unique_filename(db_usage); + } + else if(m_ifDatabaseExists == Ioss::DB_ADD_SUFFIX_OVERWRITE) + { + if(m_dbChangeCount > 0) + { std::ostringstream tmp_filename; - tmp_filename << m_ioDB; - filename = m_ioDB; - if (m_dbChangeCount > 1) { - tmp_filename << "-s" << std::setw(4) << std::setfill('0') << m_dbChangeCount; - } - size_t inc = 0; - while (file_exists(tmp_filename.str(), m_dbType, db_usage)) { - filename = tmp_filename.str(); - tmp_filename.clear(); - tmp_filename.str(""); - tmp_filename << m_ioDB << "-s" << std::setw(4) << std::setfill('0') - << m_dbChangeCount + (++inc); - } - if (inc > 0) { - m_dbChangeCount += (inc - 1); - } - else { - m_ifDatabaseExists = Ioss::DB_OVERWRITE; - } + tmp_filename << m_ioDB << "-s" << std::setw(4) << std::setfill('0') << ++m_dbChangeCount; + filename = tmp_filename.str(); } - else if (m_ifDatabaseExists == Ioss::DB_ADD_SUFFIX) { - filename = get_unique_filename(db_usage); - } - else if (m_ifDatabaseExists == Ioss::DB_ADD_SUFFIX_OVERWRITE) { - if (m_dbChangeCount > 0) { - std::ostringstream tmp_filename; - tmp_filename << m_ioDB << "-s" << std::setw(4) << std::setfill('0') - << ++m_dbChangeCount; - filename = tmp_filename.str(); - } - else { - filename = m_ioDB; - } - } - else { + else + { filename = m_ioDB; } } - else if (m_ifDatabaseExists == Ioss::DB_ADD_SUFFIX) { - filename = get_unique_filename(db_usage); - } - else { + else + { filename = m_ioDB; } - - m_properties.add(Ioss::Property("APPEND_OUTPUT", m_ifDatabaseExists)); - // A little complicated on deciding whether we are actually - // overwriting the database. The 'validate' routine for Results and - // History will call create_database once the parser block is - // ended. This routine will then create the database and the - // ioRegion_. However, the database will not really be opened or - // written to at this time. If the code is auto-restarting, then it will - // detect that the database exists and create a database with the - // -s000x extension. - // At this point, we need to skip the 'abort_if_exists' test if we - // are in this routine from the 'validate' and we are restarting - // since we won't really write to the file. So, the cases where we - // *don't* check are: - // -- is_input_event(db_usage) - // -- ifExists_ == DB_OVERWRITE || DB_ADD_SUFFIX_OVERWRITE || DB_APPEND - // -- is_automatic_restart() && step == 0 (coming from validate) - if (m_ifDatabaseExists != DB_OVERWRITE && m_ifDatabaseExists != DB_APPEND && - m_ifDatabaseExists != DB_ADD_SUFFIX_OVERWRITE && - !(step == 0 && observer->is_automatic_restart())) { - abort_if_exists(filename, m_dbType, db_usage); - } } - return filename; - } + else if(m_ifDatabaseExists == Ioss::DB_ADD_SUFFIX) + { + filename = get_unique_filename(db_usage); + } + else + { + filename = m_ioDB; + } - bool DynamicTopologyFileControl::abort_if_exists(const std::string &filename, - const std::string &db_type, - Ioss::DatabaseUsage db_usage) - { - // Check whether file with same name as database already exists. If so, - // print error message and stop... - // At the current time, only check on processor 0 and assume if it doesn't exist - // there, then it doesn't exist on other processors. Or, if it doesn't exist on - // processor 0, then it doesn't matter if it doesn't exist on other processors - // since we don't have all pieces... - - bool exists = file_exists(filename, db_type, db_usage); - if (exists) { - std::ostringstream errmsg; - fmt::print(errmsg, - "ERROR: The database file named '{} exists" - "and would be overwritten if the code continued.\n\n" - "Input options specified that this file *not* be overwritten,\n" - "\tso you must rename or remove this file and restart the code.\n", - filename); - IOSS_ERROR(errmsg); + m_properties.add(Ioss::Property("APPEND_OUTPUT", m_ifDatabaseExists)); + // A little complicated on deciding whether we are actually + // overwriting the database. The 'validate' routine for Results and + // History will call create_database once the parser block is + // ended. This routine will then create the database and the + // ioRegion_. However, the database will not really be opened or + // written to at this time. If the code is auto-restarting, then it will + // detect that the database exists and create a database with the + // -s000x extension. + // At this point, we need to skip the 'abort_if_exists' test if we + // are in this routine from the 'validate' and we are restarting + // since we won't really write to the file. So, the cases where we + // *don't* check are: + // -- is_input_event(db_usage) + // -- ifExists_ == DB_OVERWRITE || DB_ADD_SUFFIX_OVERWRITE || DB_APPEND + // -- is_automatic_restart() && step == 0 (coming from validate) + if(m_ifDatabaseExists != DB_OVERWRITE && + m_ifDatabaseExists != DB_APPEND && + m_ifDatabaseExists != DB_ADD_SUFFIX_OVERWRITE && + !(step == 0 && observer->is_automatic_restart())) + { + abort_if_exists(filename, m_dbType, db_usage); } - return exists; } + return filename; +} + +bool DynamicTopologyFileControl::abort_if_exists(const std::string &filename, + const std::string &db_type, + Ioss::DatabaseUsage db_usage) +{ + // Check whether file with same name as database already exists. If so, + // print error message and stop... + // At the current time, only check on processor 0 and assume if it doesn't exist + // there, then it doesn't exist on other processors. Or, if it doesn't exist on + // processor 0, then it doesn't matter if it doesn't exist on other processors + // since we don't have all pieces... + + bool exists = file_exists(filename, db_type, db_usage); + if (exists) { + std::ostringstream errmsg; + fmt::print(errmsg, + "ERROR: The database file named '{} exists" + "and would be overwritten if the code continued.\n\n" + "Input options specified that this file *not* be overwritten,\n" + "\tso you must rename or remove this file and restart the code.\n", + filename); + IOSS_ERROR(errmsg); + } + return exists; +} - Ioss::DatabaseIO *DynamicTopologyFileControl::clone_output_database(int steps) - { - auto current_db = m_region->get_database(); - - if (current_db->is_input()) - return nullptr; - - const Ioss::PropertyManager ¤t_properties = current_db->get_property_manager(); - Ioss::NameList names; - current_properties.describe(&names); +Ioss::DatabaseIO * DynamicTopologyFileControl::clone_output_database(int steps) +{ + auto current_db = m_region->get_database(); - // Iterate through properties and transfer to new output database... - Ioss::NameList::const_iterator I; - for (I = names.begin(); I != names.end(); ++I) { - if (!current_properties.exists(*I)) - m_properties.add(current_properties.get(*I)); - } + if (current_db->is_input()) + return nullptr; - auto db_usage = current_db->usage(); + const Ioss::PropertyManager& current_properties = current_db->get_property_manager(); + Ioss::NameList names; + current_properties.describe(&names); - std::string filename = construct_database_filename(steps, db_usage); + // Iterate through properties and transfer to new output database... + Ioss::NameList::const_iterator I; + for (I = names.begin(); I != names.end(); ++I) { + if (!current_properties.exists(*I)) + m_properties.add(current_properties.get(*I)); + } - Ioss::DatabaseIO *db = Ioss::IOFactory::create(m_dbType, filename, db_usage, - current_db->util().communicator(), m_properties); + auto db_usage = current_db->usage(); - if (nullptr == db) { - std::ostringstream errmsg; - fmt::print(errmsg, - "ERROR: unable to create output database named '{}'" - " of type '{}'", - filename, m_dbType); - IOSS_ERROR(errmsg); - } + std::string filename = construct_database_filename(steps, db_usage); - assert(db != nullptr); - if (!db->ok(true)) { - std::ostringstream errmsg; - fmt::print(errmsg, - "ERROR: unable to validate output database named '{}'" - " of type '{}'", - filename, m_dbType); - IOSS_ERROR(errmsg); - } + Ioss::DatabaseIO *db = Ioss::IOFactory::create(m_dbType, filename, db_usage, + current_db->util().communicator(), + m_properties); - db->set_field_separator(current_db->get_field_separator()); - db->set_surface_split_type(current_db->get_surface_split_type()); - db->set_maximum_symbol_length(current_db->maximum_symbol_length()); - db->set_int_byte_size_api(current_db->int_byte_size_data_size()); + if (nullptr == db) { + std::ostringstream errmsg; + fmt::print(errmsg, + "ERROR: unable to create output database named '{}'" + " of type '{}'", filename, m_dbType); + IOSS_ERROR(errmsg); + } - return db; + assert(db != nullptr); + if(!db->ok(true)) { + std::ostringstream errmsg; + fmt::print(errmsg, + "ERROR: unable to validate output database named '{}'" + " of type '{}'", filename, m_dbType); + IOSS_ERROR(errmsg); } - template - void update_database_for_grouping_entities(const T &container, Ioss::DatabaseIO *db) - { - for (auto *entity : container) { - Ioss::GroupingEntity *ge = dynamic_cast(entity); - assert(ge != nullptr); - - if (ge->type() == Ioss::SIDESET) { - Ioss::SideSet *sset = dynamic_cast(ge); - assert(sset != nullptr); - - sset->reset_database(db); - const auto &sblocks = sset->get_side_blocks(); - for (const auto &sblock : sblocks) { - sblock->reset_database(db); - } - } - else { - ge->reset_database(db); + db->set_field_separator(current_db->get_field_separator()); + db->set_surface_split_type(current_db->get_surface_split_type()); + db->set_maximum_symbol_length(current_db->maximum_symbol_length()); + db->set_int_byte_size_api(current_db->int_byte_size_data_size()); + + return db; +} + +template +void update_database_for_grouping_entities(const T& container, Ioss::DatabaseIO *db) +{ + for(auto * entity : container) { + Ioss::GroupingEntity* ge = dynamic_cast(entity); + assert(ge != nullptr); + + if(ge->type() == Ioss::SIDESET) { + Ioss::SideSet *sset = dynamic_cast(ge); + assert(sset != nullptr); + + sset->reset_database(db); + const auto &sblocks = sset->get_side_blocks(); + for (const auto &sblock : sblocks) { + sblock->reset_database(db); } + } else { + ge->reset_database(db); } } +} - bool DynamicTopologyFileControl::replace_output_database(Ioss::DatabaseIO *db) - { - auto current_db = m_region->get_database(); - - if (current_db->is_input()) - return false; - - current_db->finalize_database(); - current_db->closeDatabase(); - delete current_db; - - m_region->reset_database(db); - db->set_region(m_region); - - update_database_for_grouping_entities(m_region->get_node_blocks(), db); - update_database_for_grouping_entities(m_region->get_edge_blocks(), db); - update_database_for_grouping_entities(m_region->get_face_blocks(), db); - update_database_for_grouping_entities(m_region->get_element_blocks(), db); - update_database_for_grouping_entities(m_region->get_sidesets(), db); - update_database_for_grouping_entities(m_region->get_nodesets(), db); - update_database_for_grouping_entities(m_region->get_edgesets(), db); - update_database_for_grouping_entities(m_region->get_facesets(), db); - update_database_for_grouping_entities(m_region->get_elementsets(), db); - update_database_for_grouping_entities(m_region->get_commsets(), db); - update_database_for_grouping_entities(m_region->get_structured_blocks(), db); - update_database_for_grouping_entities(m_region->get_assemblies(), db); - update_database_for_grouping_entities(m_region->get_blobs(), db); - - return true; - } +bool DynamicTopologyFileControl::replace_output_database(Ioss::DatabaseIO *db) +{ + auto current_db = m_region->get_database(); - void DynamicTopologyFileControl::clone_and_replace_output_database(int steps) - { - auto db = clone_output_database(steps); + if (current_db->is_input()) + return false; - if (nullptr != db) - replace_output_database(db); - } + current_db->finalize_database(); + current_db->closeDatabase(); + delete current_db; + + m_region->reset_database(db); + db->set_region(m_region); + + update_database_for_grouping_entities(m_region->get_node_blocks(), db); + update_database_for_grouping_entities(m_region->get_edge_blocks(), db); + update_database_for_grouping_entities(m_region->get_face_blocks(), db); + update_database_for_grouping_entities(m_region->get_element_blocks(), db); + update_database_for_grouping_entities(m_region->get_sidesets(), db); + update_database_for_grouping_entities(m_region->get_nodesets(), db); + update_database_for_grouping_entities(m_region->get_edgesets(), db); + update_database_for_grouping_entities(m_region->get_facesets(), db); + update_database_for_grouping_entities(m_region->get_elementsets(), db); + update_database_for_grouping_entities(m_region->get_commsets(), db); + update_database_for_grouping_entities(m_region->get_structured_blocks(), db); + update_database_for_grouping_entities(m_region->get_assemblies(), db); + update_database_for_grouping_entities(m_region->get_blobs(), db); + + return true; +} + +void DynamicTopologyFileControl::clone_and_replace_output_database(int steps) +{ + auto db = clone_output_database(steps); + + if(nullptr != db) + replace_output_database(db); +} + +void DynamicTopologyFileControl::add_output_database_group(int steps) +{ + auto current_db = m_region->get_database(); + + std::ostringstream oss; + oss << group_prefix(); + oss << m_dbChangeCount; + + current_db->release_memory(); + current_db->open_root_group(); + current_db->create_subgroup(oss.str()); + + m_dbChangeCount++; +} + +} - void DynamicTopologyFileControl::add_output_database_group(int steps) - { - auto current_db = m_region->get_database(); - std::ostringstream oss; - oss << "STEP-"; - oss << steps; - current_db->release_memory(); - current_db->open_root_group(); - current_db->create_subgroup(oss.str()); - } -} // namespace Ioss diff --git a/packages/seacas/libraries/ioss/src/Ioss_DynamicTopology.h b/packages/seacas/libraries/ioss/src/Ioss_DynamicTopology.h index 13cd277c12..b166cfb580 100644 --- a/packages/seacas/libraries/ioss/src/Ioss_DynamicTopology.h +++ b/packages/seacas/libraries/ioss/src/Ioss_DynamicTopology.h @@ -20,10 +20,12 @@ #include // for ostream #include -#include // for string, operator< +#include // for string, operator< +#include namespace Ioss { class Region; + class DynamicTopologyNotifier; /*! The TopologyModified enumeration is used as an argument to the * topology_modified() functions in io to @@ -42,20 +44,16 @@ namespace Ioss { * - TOPOLOGY_UNKNOWN: Something else, catchall option. */ enum TopologyModified { - TOPOLOGY_SAME = (0), //!< No change, also used for initialization - TOPOLOGY_REORDER = - (1U << 0), //!< Data structures reordered on processor, no change between procs. - TOPOLOGY_SHUFFLE = (1U << 1), //!< Globally the same, data moved among processors. - TOPOLOGY_HADAPT = (1U << 2), //!< Elements split/combined; not moved cross-proc - TOPOLOGY_GHOST = (1U << 3), //!< Ghost entities created/destroyed - TOPOLOGY_GEOMETRY = - (1U << 4), //!< Geometry (mesh coordinates) modified. Restart needs to know this. - TOPOLOGY_CREATEFACE = (1U << 5), //!< Face/Edge are created/deleted. - TOPOLOGY_CREATEELEM = (1U << 6), //!< Elements are created/deleted. - TOPOLOGY_CREATENODE = (1U << 7), //!< Nodes are created/deleted. - TOPOLOGY_UNKNOWN = (1U << 8), //!< Unknown change, recreate from scratch. - TOPOLOGY_AUXILIARY = (1U << 9), //!< An AUXILIARY relation was created/modified. - TOPOLOGY_CONSTRAINT = (1U << 10) //!< Contact constraints + TOPOLOGY_SAME = ( 0), //!< No change, also used for initialization + TOPOLOGY_REORDER = (1U << 0), //!< Data structures reordered on processor, no change between procs. + TOPOLOGY_SHUFFLE = (1U << 1), //!< Globally the same, data moved among processors. + TOPOLOGY_HADAPT = (1U << 2), //!< Elements split/combined; not moved cross-proc + TOPOLOGY_GEOMETRY = (1U << 3), //!< Geometry (mesh coordinates) modified. Restart needs to know this. + TOPOLOGY_CREATEFACE = (1U << 4), //!< Face/Edge are created/deleted. + TOPOLOGY_CREATEELEM = (1U << 5), //!< Elements are created/deleted. + TOPOLOGY_CREATENODE = (1U << 6), //!< Nodes are created/deleted. + TOPOLOGY_CREATEASSEMBLY = (1U << 7), //!< Assemblies are created/deleted. + TOPOLOGY_UNKNOWN = (1U << 8), //!< Unknown change, recreate from scratch. }; enum class FileControlOption { CONTROL_NONE, CONTROL_AUTO_MULTI_FILE, CONTROL_AUTO_SINGLE_FILE }; @@ -63,13 +61,14 @@ namespace Ioss { class IOSS_EXPORT DynamicTopologyObserver { public: - DynamicTopologyObserver(Region *region) : m_region(region) {} - DynamicTopologyObserver() {} + DynamicTopologyObserver(Region *region) + : m_region(region) {} virtual ~DynamicTopologyObserver() {} - virtual void reset_topology_modification(); - virtual void set_topology_modification(unsigned int type); + virtual void reset_topology_modification_all(); + virtual void reset_topology_modification(); + virtual void set_topology_modification(unsigned int type); virtual unsigned int get_topology_modification() const; virtual unsigned int get_cumulative_topology_modification() const; @@ -89,14 +88,14 @@ namespace Ioss { void register_region(Region *region); Region *get_region() const { return m_region; } - virtual void define_model(Region ®ion); - virtual void write_model(Region ®ion); - virtual void define_transient(Region ®ion); + void register_notifier(DynamicTopologyNotifier *notifier); + DynamicTopologyNotifier* get_notifier() const { return m_notifier; } - virtual FileControlOption get_control_option() const - { - return FileControlOption::CONTROL_AUTO_MULTI_FILE; - } + virtual void define_model(); + virtual void write_model(); + virtual void define_transient(); + + virtual FileControlOption get_control_option() const { return FileControlOption::CONTROL_NONE; } protected: Region *m_region{nullptr}; @@ -106,11 +105,100 @@ namespace Ioss { bool m_automaticRestart{false}; bool m_restartRequested{false}; - void check_region() const; + DynamicTopologyNotifier *m_notifier{nullptr}; + + void check_region() const; IOSS_NODISCARD const ParallelUtils &util() const; - void synchronize_topology_modified_flags(); + void synchronize_topology_modified_flags(); + + void set_topology_modification_nl(unsigned int type); + + private: + DynamicTopologyObserver(); }; + + class IOSS_EXPORT DynamicTopologyNotifier + { + public: + DynamicTopologyNotifier(const std::string& model_name) + : m_modelName(model_name) {} + + virtual ~DynamicTopologyNotifier() = default; + + std::string name() const { return m_modelName; } + + std::vector> get_observers() const { return m_observers; } + + void register_observer(std::shared_ptr observer); + + void unregister_observer(std::shared_ptr observer); + + void reset_topology_modification(); + + void set_topology_modification(unsigned int type); + + template + bool has_observer_type() const + { + bool found = false; + + for(const std::shared_ptr& observer : m_observers) { + if (dynamic_cast(observer.get()) != nullptr) { + found = true; + break; + } + } + return found; + } + + template + std::vector> get_observer_type() const + { + std::vector> typed_observers; + + for(const std::shared_ptr &observer : m_observers) { + ObserverType* typed_observer = dynamic_cast(observer.get()); + if (typed_observer != nullptr) { + typed_observers.push_back(std::dynamic_pointer_cast(observer)); + } + } + + return typed_observers; + } + + private: + const std::string m_modelName; + std::vector> m_observers; + }; + + + class IOSS_EXPORT DynamicTopologyBroker + { + public: + static DynamicTopologyBroker *broker(); + + void register_model(const std::string& model_name); + void remove_model(const std::string& model_name); + void clear_models(); + + std::shared_ptr get_notifier(const std::string& model_name) const; + std::vector> get_observers(const std::string& model_name) const; + + void register_observer(const std::string& model_name, std::shared_ptr observer); + void register_observer(const std::string& model_name, std::shared_ptr observer, Region& region); + + void reset_topology_modification(const std::string& model_name); + void set_topology_modification(const std::string& model_name, unsigned int type); + + private: + DynamicTopologyBroker() {}; + DynamicTopologyBroker(DynamicTopologyBroker&); + + std::map> m_notifiers; + }; + + class IOSS_EXPORT DynamicTopologyFileControl { public: @@ -121,6 +209,8 @@ namespace Ioss { void clone_and_replace_output_database(int steps = 0); void add_output_database_group(int steps = 0); + static std::string group_prefix() { return "IO_FILE_GROUP-"; } + private: Region *m_region{nullptr}; std::string m_ioDB; diff --git a/packages/seacas/libraries/ioss/src/Ioss_Region.C b/packages/seacas/libraries/ioss/src/Ioss_Region.C index 5457078cd5..b2420ac6c2 100644 --- a/packages/seacas/libraries/ioss/src/Ioss_Region.C +++ b/packages/seacas/libraries/ioss/src/Ioss_Region.C @@ -386,6 +386,10 @@ namespace Ioss { } catch (...) { } + + if(topologyObserver) { + topologyObserver->register_region(nullptr); + } } void Region::reset_region() @@ -713,9 +717,9 @@ namespace Ioss { int steps = get_property("state_count").get_int(); start_new_output_database_entry(steps); - topologyObserver->define_model(*this); - topologyObserver->write_model(*this); - topologyObserver->define_transient(*this); + topologyObserver->define_model(); + topologyObserver->write_model(); + topologyObserver->define_transient(); topologyObserver->reset_topology_modification(); } } @@ -2933,6 +2937,29 @@ namespace Ioss { } } + void Region::reset_topology_modification() + { + if(topologyObserver) { + topologyObserver->reset_topology_modification(); + } + } + + void Region::set_topology_modification(unsigned int type) + { + if(topologyObserver) { + topologyObserver->set_topology_modification(type); + } + } + + unsigned int Region::get_topology_modification() const + { + if(topologyObserver) { + return topologyObserver->get_topology_modification(); + } + + return TOPOLOGY_SAME; + } + bool Region::load_group_mesh(const std::string &group_name) { // Check name for '/' which is not allowed since it is the diff --git a/packages/seacas/libraries/ioss/src/Ioss_Region.h b/packages/seacas/libraries/ioss/src/Ioss_Region.h index 5e62bab876..4e86a5b8b0 100644 --- a/packages/seacas/libraries/ioss/src/Ioss_Region.h +++ b/packages/seacas/libraries/ioss/src/Ioss_Region.h @@ -290,6 +290,10 @@ namespace Ioss { return topologyObserver; } + void reset_topology_modification(); + void set_topology_modification(unsigned int type); + unsigned int get_topology_modification() const; + void start_new_output_database_entry(int steps = 0); void set_topology_change_count(unsigned int new_count) { dbChangeCount = new_count; } diff --git a/packages/seacas/libraries/ioss/src/exodus/Ioex_BaseDatabaseIO.C b/packages/seacas/libraries/ioss/src/exodus/Ioex_BaseDatabaseIO.C index 5733bdf694..5cb2caec64 100644 --- a/packages/seacas/libraries/ioss/src/exodus/Ioex_BaseDatabaseIO.C +++ b/packages/seacas/libraries/ioss/src/exodus/Ioex_BaseDatabaseIO.C @@ -507,6 +507,7 @@ namespace Ioex { // Get existing file pointer... bool success = false; + Ioss::SerializeIO serializeIO_(this); int exoid = get_file_pointer(); int group_name_length = ex_inquire_int(exoid, EX_INQ_GROUP_NAME_LEN); @@ -541,6 +542,7 @@ namespace Ioex { // Get existing file pointer... bool success = false; + Ioss::SerializeIO serializeIO_(this); int exoid = get_file_pointer(); m_groupName = group_name; @@ -561,6 +563,7 @@ namespace Ioex { bool success = false; if (!is_input()) { // Get existing file pointer... + Ioss::SerializeIO serializeIO_(this); int exoid = get_file_pointer(); // Check name for '/' which is not allowed since it is the @@ -3252,6 +3255,52 @@ namespace Ioex { activeNodeSetNodesIndex.clear(); } + + int BaseDatabaseIO::num_child_group_nl() + { + Ioss::SerializeIO serializeIO_(this); + int exoid = get_file_pointer(); + exoid = ex_inquire_int(exoid, EX_INQ_GROUP_ROOT); + int num_children = ex_inquire_int(exoid, EX_INQ_NUM_CHILD_GROUPS); + return num_children; + } + + bool BaseDatabaseIO::open_child_group_nl(int index) + { + if(index < 0) return false; + Ioss::SerializeIO serializeIO_(this); + int exoid = get_file_pointer(); + int num_children = ex_inquire_int(exoid, EX_INQ_NUM_CHILD_GROUPS); + if(num_children == 0) return true; + + if(index >= num_children) return false; + + std::vector children(num_children); + + int ierr = ex_get_group_ids(exoid, nullptr, Data(children)); + if (ierr < 0) { + Ioex::exodus_error(exoid, __LINE__, __func__, __FILE__); + } + + exoid = children[index]; + + int group_name_length = ex_inquire_int(exoid, EX_INQ_GROUP_NAME_LEN); + std::vector group_name(group_name_length+1, '\0'); + + // Get name of this group... + int idum; + float rdum; + ierr = ex_inquire(exoid, EX_INQ_GROUP_NAME, &idum, &rdum, group_name.data()); + if (ierr < 0) { + Ioex::exodus_error(exoid, __LINE__, __func__, __FILE__); + } + + m_exodusFilePtr = exoid; + m_groupName = std::string(group_name.data()); + + return true; + } + } // namespace Ioex namespace { diff --git a/packages/seacas/libraries/ioss/src/exodus/Ioex_BaseDatabaseIO.h b/packages/seacas/libraries/ioss/src/exodus/Ioex_BaseDatabaseIO.h index 11174e6052..b6f18fc756 100644 --- a/packages/seacas/libraries/ioss/src/exodus/Ioex_BaseDatabaseIO.h +++ b/packages/seacas/libraries/ioss/src/exodus/Ioex_BaseDatabaseIO.h @@ -101,6 +101,8 @@ namespace Ioex { void release_memory_nl() override; + int num_child_group_nl() override; + bool open_child_group_nl(int index) override; bool open_root_group_nl() override; bool open_group_nl(const std::string &group_name) override; bool create_subgroup_nl(const std::string &group_name) override; diff --git a/packages/seacas/libraries/ioss/src/exodus/Ioex_DatabaseIO.C b/packages/seacas/libraries/ioss/src/exodus/Ioex_DatabaseIO.C index fb3f0ac6c0..d368bd2ff9 100644 --- a/packages/seacas/libraries/ioss/src/exodus/Ioex_DatabaseIO.C +++ b/packages/seacas/libraries/ioss/src/exodus/Ioex_DatabaseIO.C @@ -171,6 +171,9 @@ namespace Ioex { IOSS_ERROR(errmsg); } } + + open_root_group_nl(); + open_child_group_nl(0); } bool DatabaseIO::check_valid_file_ptr(bool write_message, std::string *error_msg, int *bad_count, diff --git a/packages/seacas/libraries/ioss/src/init/Ionit_Initializer.C b/packages/seacas/libraries/ioss/src/init/Ionit_Initializer.C index 9018a7af82..f589b7f134 100644 --- a/packages/seacas/libraries/ioss/src/init/Ionit_Initializer.C +++ b/packages/seacas/libraries/ioss/src/init/Ionit_Initializer.C @@ -47,6 +47,8 @@ #include "Ioss_IOFactory.h" +#include "Ioss_DynamicTopology.h" + namespace { #if defined(IOSS_THREADSAFE) std::mutex m_; @@ -94,6 +96,7 @@ namespace Ioss::Init { Iogs::IOFactory::factory(); // Structured Mesh Generator Ionull::IOFactory::factory(); Ioss::StorageInitializer(); + Ioss::DynamicTopologyBroker::broker(); Ioss::Initializer(); Iotr::Initializer(); #ifdef HAVE_SEACASIOSS_ADIOS2 diff --git a/packages/seacas/libraries/ioss/src/unit_tests/UnitTestDynamicTopology.C b/packages/seacas/libraries/ioss/src/unit_tests/UnitTestDynamicTopology.C index 852b86a25f..6d821679dc 100644 --- a/packages/seacas/libraries/ioss/src/unit_tests/UnitTestDynamicTopology.C +++ b/packages/seacas/libraries/ioss/src/unit_tests/UnitTestDynamicTopology.C @@ -21,649 +21,1091 @@ #include // for unlink #include "Ionit_Initializer.h" -#include "Ioss_DBUsage.h" #include "Ioss_DatabaseIO.h" // for DatabaseIO +#include "Ioss_DBUsage.h" #include "Ioss_ElementBlock.h" -#include "Ioss_Field.h" // for Field, etc +#include "Ioss_Field.h" // for Field, etc #include "Ioss_FileInfo.h" #include "Ioss_IOFactory.h" #include "Ioss_NodeBlock.h" #include "Ioss_ParallelUtils.h" #include "Ioss_Property.h" #include "Ioss_Region.h" - #include "Ioss_Utils.h" #include "exodus/Ioex_DatabaseIO.h" namespace { - std::string get_many_block_mesh_desc(unsigned numBlocks) - { - std::ostringstream oss; - std::vector elementIds(numBlocks); - std::iota(elementIds.begin(), elementIds.end(), 1); - - unsigned proc = 0; - for (unsigned i = 0; i < numBlocks; ++i) { - unsigned elemId = elementIds[i]; - unsigned firstNodeId = i * 4 + 1; - oss << proc << "," << elemId << ",HEX_8,"; - for (unsigned node = firstNodeId; node < firstNodeId + 8; ++node) { - oss << node << ","; - } - unsigned blockId = i + 1; - oss << "block_" << blockId; - - if (i < numBlocks - 1) { - oss << "\n"; - } +std::string get_many_block_mesh_desc(unsigned numBlocks) +{ + std::ostringstream oss; + std::vector elementIds(numBlocks); + std::iota(elementIds.begin(), elementIds.end(), 1); + + unsigned proc = 0; + for (unsigned i = 0; i < numBlocks; ++i) { + unsigned elemId = elementIds[i]; + unsigned firstNodeId = i * 4 + 1; + oss << proc << "," << elemId << ",HEX_8,"; + for (unsigned node = firstNodeId; node < firstNodeId + 8; ++node) { + oss << node << ","; + } + unsigned blockId = i + 1; + oss << "block_" << blockId; - proc++; + if (i < numBlocks - 1) { + oss << "\n"; } - oss << "|coordinates:"; + proc++; + } + + oss << "|coordinates:"; - std::vector planeCoords = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0}; + std::vector planeCoords = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0}; + + for (double coord : planeCoords) { + oss << coord << ","; + } + + for (unsigned i = 1; i <= numBlocks; ++i) { + for (unsigned point = 0; point < 4; ++point) { + planeCoords[3 * point + 2] += 1; + } for (double coord : planeCoords) { oss << coord << ","; } + } - for (unsigned i = 1; i <= numBlocks; ++i) { - for (unsigned point = 0; point < 4; ++point) { - planeCoords[3 * point + 2] += 1; - } + return oss.str(); +} - for (double coord : planeCoords) { - oss << coord << ","; - } - } +void define_model(const Ioss::Region &i_region, Ioss::Region &o_region) +{ + Ioss::DatabaseIO *o_database = o_region.get_database(); + + o_region.begin_mode(Ioss::STATE_DEFINE_MODEL); + + auto& nodeblocks = o_region.get_node_blocks(); - return oss.str(); + Ioss::NodeBlock *i_nb = i_region.get_node_blocks()[0]; + int64_t spatial_dim = 3; + int64_t num_nodes = i_nb->entity_count(); + Ioss::NodeBlock *o_nb = new Ioss::NodeBlock(o_database, "nodeblock_1", num_nodes, spatial_dim); + o_region.add(o_nb); + + for (Ioss::ElementBlock *i_eb : i_region.get_element_blocks()) { + Ioss::ElementBlock *o_eb = new Ioss::ElementBlock( + o_database, i_eb->name(), i_eb->topology()->name(), i_eb->entity_count()); + o_eb->property_add(i_eb->get_property("id")); + o_region.add(o_eb); } - void define_model(const Ioss::Region &i_region, Ioss::Region &o_region) - { - Ioss::DatabaseIO *o_database = o_region.get_database(); + o_region.end_mode(Ioss::STATE_DEFINE_MODEL); +} - o_region.begin_mode(Ioss::STATE_DEFINE_MODEL); +void write_model(const Ioss::Region &i_region, Ioss::Region &o_region) +{ + Ioss::NodeBlock *i_nb = i_region.get_node_blocks()[0]; + Ioss::NodeBlock *o_nb = o_region.get_node_blocks()[0]; - Ioss::NodeBlock *i_nb = i_region.get_node_blocks()[0]; - int64_t spatial_dim = 3; - int64_t num_nodes = i_nb->entity_count(); - Ioss::NodeBlock *o_nb = new Ioss::NodeBlock(o_database, "nodeblock_1", num_nodes, spatial_dim); - o_region.add(o_nb); + o_region.begin_mode(Ioss::STATE_MODEL); + std::vector coordinates; + std::vector node_ids; + i_nb->get_field_data("ids", node_ids); + i_nb->get_field_data("mesh_model_coordinates", coordinates); - for (Ioss::ElementBlock *i_eb : i_region.get_element_blocks()) { - Ioss::ElementBlock *o_eb = new Ioss::ElementBlock( - o_database, i_eb->name(), i_eb->topology()->name(), i_eb->entity_count()); - o_eb->property_add(i_eb->get_property("id")); - o_region.add(o_eb); - } + o_nb->put_field_data("ids", node_ids); + o_nb->put_field_data("mesh_model_coordinates", coordinates); + + for (Ioss::ElementBlock *i_eb : i_region.get_element_blocks()) { + Ioss::ElementBlock *o_eb = o_region.get_element_block(i_eb->name()); + std::vector elem_ids; + std::vector connectivity; + + i_eb->get_field_data("ids", elem_ids); + i_eb->get_field_data("connectivity", connectivity); - o_region.end_mode(Ioss::STATE_DEFINE_MODEL); + o_eb->put_field_data("ids", elem_ids); + o_eb->put_field_data("connectivity", connectivity); } - void write_model(const Ioss::Region &i_region, Ioss::Region &o_region) - { - Ioss::NodeBlock *i_nb = i_region.get_node_blocks()[0]; - Ioss::NodeBlock *o_nb = o_region.get_node_blocks()[0]; + o_region.end_mode(Ioss::STATE_MODEL); +} + +void define_transient(const Ioss::Region &i_region, Ioss::Region &o_region, + const std::string &elemFieldName) +{ + o_region.begin_mode(Ioss::STATE_DEFINE_TRANSIENT); - o_region.begin_mode(Ioss::STATE_MODEL); - std::vector coordinates; - std::vector node_ids; - i_nb->get_field_data("ids", node_ids); - i_nb->get_field_data("mesh_model_coordinates", coordinates); + for (Ioss::ElementBlock *o_eb : o_region.get_element_blocks()) { + size_t num_elem = o_eb->get_property("entity_count").get_int(); + std::string storage = "scalar"; - o_nb->put_field_data("ids", node_ids); - o_nb->put_field_data("mesh_model_coordinates", coordinates); + Ioss::Field field(elemFieldName, Ioss::Field::REAL, storage, 1, Ioss::Field::Field::TRANSIENT, + num_elem); + o_eb->field_add(field); + } + o_region.end_mode(Ioss::STATE_DEFINE_TRANSIENT); +} - for (Ioss::ElementBlock *i_eb : i_region.get_element_blocks()) { - Ioss::ElementBlock *o_eb = o_region.get_element_block(i_eb->name()); - std::vector elem_ids; - std::vector connectivity; +int write_transient(Ioss::Region &o_region, const std::string &elemFieldName, const double time) +{ + o_region.begin_mode(Ioss::STATE_TRANSIENT); + int step = o_region.add_state(time); + o_region.begin_state(step); - i_eb->get_field_data("ids", elem_ids); - i_eb->get_field_data("connectivity", connectivity); + for (Ioss::ElementBlock *o_eb : o_region.get_element_blocks()) { + size_t num_elem = o_eb->get_property("entity_count").get_int(); - o_eb->put_field_data("ids", elem_ids); - o_eb->put_field_data("connectivity", connectivity); + std::vector field_data(num_elem); + std::vector elem_ids; + + o_eb->get_field_data("ids", elem_ids); + for (size_t i = 0; i < elem_ids.size(); i++) { + field_data[i] = (double)elem_ids[i] + 100*time; } - o_region.end_mode(Ioss::STATE_MODEL); + o_eb->put_field_data(elemFieldName, field_data); } - void define_transient(const Ioss::Region &, Ioss::Region &o_region, - const std::string &elemFieldName) + o_region.end_state(step); + o_region.end_mode(Ioss::STATE_TRANSIENT); + + return step; +} + +class Observer : public Ioss::DynamicTopologyObserver +{ +public: + Observer(Ioss::Region& inputRegion_, + const std::string &elemFieldName_, + const Ioss::FileControlOption fileControlOption_) + : Ioss::DynamicTopologyObserver(nullptr) + , inputRegion(inputRegion_) + , elemFieldName(elemFieldName_) + , fileControlOption(fileControlOption_) + {} + + virtual ~Observer() {} + + void define_model() override { - o_region.begin_mode(Ioss::STATE_DEFINE_TRANSIENT); + ::define_model(inputRegion, *get_region()); + } - for (Ioss::ElementBlock *o_eb : o_region.get_element_blocks()) { - size_t num_elem = o_eb->get_property("entity_count").get_int(); - std::string storage = "scalar"; + void write_model() override + { + ::write_model(inputRegion, *get_region()); + } - Ioss::Field field(elemFieldName, Ioss::Field::REAL, storage, 1, Ioss::Field::Field::TRANSIENT, - num_elem); - o_eb->field_add(field); - } - o_region.end_mode(Ioss::STATE_DEFINE_TRANSIENT); + void define_transient() override + { + ::define_transient(inputRegion, *get_region(), elemFieldName); } - int write_transient(Ioss::Region &o_region, const std::string &elemFieldName, const double time) + Ioss::FileControlOption get_control_option() const { - o_region.begin_mode(Ioss::STATE_TRANSIENT); - int step = o_region.add_state(time); - o_region.begin_state(step); + return fileControlOption; + } - for (Ioss::ElementBlock *o_eb : o_region.get_element_blocks()) { - size_t num_elem = o_eb->get_property("entity_count").get_int(); +private: + Observer(); - std::vector field_data(num_elem); - std::vector elem_ids; + Ioss::Region& inputRegion; + const std::string elemFieldName; + Ioss::FileControlOption fileControlOption; +}; - o_eb->get_field_data("ids", elem_ids); - for (size_t i = 0; i < elem_ids.size(); i++) { - field_data[i] = (double)elem_ids[i] + 100 * time; - } +struct OutputParams { + OutputParams(const std::string& outFile_) + : outFile(outFile_) {} - o_eb->put_field_data(elemFieldName, field_data); + OutputParams(const std::string& outFile_, const std::string& elemFieldName_) + : outFile(outFile_) + , elemFieldName(elemFieldName_) {} + + void set_data(const std::vector& output_times_, + const std::vector& output_steps_, + const std::vector& modification_steps_) + { + ASSERT_EQ(output_times_.size(), output_steps_.size()); + ASSERT_EQ(output_times_.size(), modification_steps_.size()); + + size_t numSteps = output_times_.size(); + for(auto i=1; i output_times_[i-1]); } - o_region.end_state(step); - o_region.end_mode(Ioss::STATE_TRANSIENT); + output_times = output_times_; + output_steps = output_steps_; + modification_steps = modification_steps_; + } + + void set_data(const std::vector& output_steps_, + const std::vector& modification_steps_) + { + ASSERT_EQ(output_steps_.size(), modification_steps_.size()); - return step; + size_t numSteps = output_steps_.size(); + for(size_t i=0; i 0) { + // Monotone increasing + EXPECT_TRUE(time > output_times[numSteps-1]); } - virtual ~Observer() {} + output_times.push_back(time); + output_steps.push_back(do_output); + modification_steps.push_back(do_modification); - void define_model(Ioss::Region ®ion) override { ::define_model(inputRegion, region); } + return *this; + } - void write_model(Ioss::Region ®ion) override { ::write_model(inputRegion, region); } + void clear() + { + output_times.clear(); + output_steps.clear(); + modification_steps.clear(); + } - void define_transient(Ioss::Region ®ion) override - { - ::define_transient(inputRegion, region, elemFieldName); + std::string outFile{"file.g"}; + std::string elemFieldName{"elem_field"}; + std::vector output_times; + std::vector output_steps; + std::vector modification_steps; +}; + +void do_output(Ioss::Region &o_region, + const OutputParams& params, + size_t step, + double& minTime, + int& maxStep, + bool& doneOutputAfterModification) +{ + if(params.output_steps[step]) { + if(!doneOutputAfterModification) { + minTime = params.output_times[step]; } - Ioss::FileControlOption get_control_option() const override { return fileControlOption; } + write_transient(o_region, params.elemFieldName, params.output_times[step]); - private: - Observer(); + auto min_result = o_region.get_min_time(); + EXPECT_EQ(1, min_result.first); + EXPECT_NEAR(minTime, min_result.second, 1.0e-6); - Ioss::Region &inputRegion; - const std::string elemFieldName; - Ioss::FileControlOption fileControlOption; - }; + auto max_result = o_region.get_max_time(); + EXPECT_EQ(maxStep, max_result.first); + EXPECT_NEAR(params.output_times[step], max_result.second, 1.0e-6); - void run_simple_topology_change(const Ioss::Region &i_region, Ioss::Region &o_region, - std::shared_ptr observer, - const std::string &elemFieldName) + maxStep++; + doneOutputAfterModification = true; + } +} + +void run_topology_change(const Ioss::Region& i_region, + Ioss::Region &o_region, + const OutputParams& params) +{ + auto observer = o_region.get_mesh_modification_observer(); + + define_model(i_region, o_region); + write_model(i_region, o_region); + + define_transient(i_region, o_region, params.elemFieldName); + + auto numSteps = params.output_steps.size(); + + int maxStep = 1; + + double minTime = numSteps > 0 ? params.output_times[0] : 0.0; + double maxTime = numSteps > 0 ? params.output_times[0] : 0.0; + + bool doneOutputAfterModification = true; + + for(size_t i=0; iset_topology_modification(Ioss::TOPOLOGY_UNKNOWN); + maxStep = 1; + doneOutputAfterModification = false; + } - define_transient(i_region, o_region, elemFieldName); + do_output(o_region, params, i, minTime, maxStep, doneOutputAfterModification); + } +} - double time = 0.0; - write_transient(o_region, elemFieldName, time); +void cleanup_simple_multi_files(const std::string &outFile) +{ + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); - auto min_result1 = o_region.get_min_time(); - EXPECT_EQ(1, min_result1.first); - EXPECT_NEAR(0.0, min_result1.second, 1.0e-6); + std::string file1 = Ioss::Utils::decode_filename(outFile, util.parallel_rank(), util.parallel_size()); + unlink(file1.c_str()); - auto max_result1 = o_region.get_max_time(); - EXPECT_EQ(1, max_result1.first); - EXPECT_NEAR(0.0, max_result1.second, 1.0e-6); + std::string file2 = Ioss::Utils::decode_filename(outFile + "-s0002", util.parallel_rank(), util.parallel_size()); + unlink(file2.c_str()); - observer->set_topology_modification(Ioss::TOPOLOGY_UNKNOWN); + std::string file3 = Ioss::Utils::decode_filename(outFile + "-s0003", util.parallel_rank(), util.parallel_size()); + unlink(file3.c_str()); - time = 1.0; - write_transient(o_region, elemFieldName, time); + std::string file4 = Ioss::Utils::decode_filename(outFile + "-s0004", util.parallel_rank(), util.parallel_size()); + unlink(file4.c_str()); +} - auto min_result2 = o_region.get_min_time(); - EXPECT_EQ(1, min_result2.first); - EXPECT_NEAR(1.0, min_result2.second, 1.0e-6); +void run_multi_file_simple_topology_change(const OutputParams& params) +{ + Ioss::Init::Initializer io; + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); - auto max_result2 = o_region.get_max_time(); - EXPECT_EQ(1, max_result2.first); - EXPECT_NEAR(1.0, max_result2.second, 1.0e-6); + int numBlocks = util.parallel_size(); - observer->set_topology_modification(Ioss::TOPOLOGY_SAME); + std::string meshDesc = get_many_block_mesh_desc(numBlocks); - time = 2.0; - write_transient(o_region, elemFieldName, time); + Ioss::PropertyManager propertyManager; - auto min_result3 = o_region.get_min_time(); - EXPECT_EQ(1, min_result3.first); - EXPECT_NEAR(1.0, min_result3.second, 1.0e-6); + Ioss::DatabaseIO *i_database = Ioss::IOFactory::create("textmesh", meshDesc, Ioss::READ_MODEL, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region i_region(i_database, "input_model"); + EXPECT_TRUE(i_database != nullptr); + EXPECT_TRUE(i_database->ok(true)); - auto max_result3 = o_region.get_max_time(); - EXPECT_EQ(2, max_result3.first); - EXPECT_NEAR(2.0, max_result3.second, 1.0e-6); - } + Ioss::DatabaseIO *o_database = Ioss::IOFactory::create("exodus", params.outFile, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region o_region(o_database, "output_model"); + EXPECT_TRUE(o_database != nullptr); + EXPECT_TRUE(o_database->ok(true)); - void cleanup_multi_files(const std::string &outFile) - { - Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + auto fileControlOption = Ioss::FileControlOption::CONTROL_AUTO_MULTI_FILE; + auto observer = std::make_shared(i_region, params.elemFieldName, fileControlOption); + o_region.register_mesh_modification_observer(observer); - std::string file1 = - Ioss::Utils::decode_filename(outFile, util.parallel_rank(), util.parallel_size()); - unlink(file1.c_str()); + run_topology_change(i_region, o_region, params); +} - std::string file2 = Ioss::Utils::decode_filename(outFile + "-s0002", util.parallel_rank(), - util.parallel_size()); - unlink(file2.c_str()); - } +TEST(TestDynamicWrite, multi_file_simple_topology_modification) +{ + std::string outFile("multiFileManyBlocks.g"); + std::string elemFieldName = "elem_field"; - void run_multi_file_simple_topology_change(const std::string &elemFieldName, - const std::string &outFile) - { - Ioss::Init::Initializer io; - Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + OutputParams params(outFile, elemFieldName); - int numBlocks = util.parallel_size(); + std::vector output_steps{true , true, true, true, true, true}; + std::vector modification_steps{false, true, false, true, true, false}; - std::string meshDesc = get_many_block_mesh_desc(numBlocks); + params.set_data(output_steps, modification_steps); - Ioss::PropertyManager propertyManager; + cleanup_simple_multi_files(outFile); + run_multi_file_simple_topology_change(params); + cleanup_simple_multi_files(outFile); +} - Ioss::DatabaseIO *i_database = Ioss::IOFactory::create( - "textmesh", meshDesc, Ioss::READ_MODEL, Ioss::ParallelUtils::comm_world(), propertyManager); - Ioss::Region i_region(i_database, "input_model"); - EXPECT_TRUE(i_database != nullptr); - EXPECT_TRUE(i_database->ok(true)); +void cleanup_cyclic_multi_files(const std::string &outFile) +{ + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); - Ioss::DatabaseIO *o_database = Ioss::IOFactory::create( - "exodus", outFile, Ioss::WRITE_RESULTS, Ioss::ParallelUtils::comm_world(), propertyManager); - Ioss::Region o_region(o_database, "output_model"); - EXPECT_TRUE(o_database != nullptr); - EXPECT_TRUE(o_database->ok(true)); + std::string file1 = Ioss::Utils::decode_filename(outFile, util.parallel_rank(), util.parallel_size()); + unlink(file1.c_str()); + + std::string file2 = Ioss::Utils::decode_filename(outFile + "-A", util.parallel_rank(), util.parallel_size()); + unlink(file2.c_str()); + + std::string file3 = Ioss::Utils::decode_filename(outFile + "-B", util.parallel_rank(), util.parallel_size()); + unlink(file3.c_str()); + + std::string file4 = Ioss::Utils::decode_filename(outFile + "-C", util.parallel_rank(), util.parallel_size()); + unlink(file4.c_str()); +} + +void run_multi_file_cyclic_topology_change(const OutputParams& params) +{ + Ioss::Init::Initializer io; + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + + int numBlocks = util.parallel_size(); + + std::string meshDesc = get_many_block_mesh_desc(numBlocks); + + Ioss::PropertyManager propertyManager; + + Ioss::DatabaseIO *i_database = Ioss::IOFactory::create("textmesh", meshDesc, Ioss::READ_MODEL, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region i_region(i_database, "input_model"); + EXPECT_TRUE(i_database != nullptr); + EXPECT_TRUE(i_database->ok(true)); + + Ioss::DatabaseIO *o_database = Ioss::IOFactory::create("exodus", params.outFile, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region o_region(o_database, "output_model"); + EXPECT_TRUE(o_database != nullptr); + EXPECT_TRUE(o_database->ok(true)); + + auto fileControlOption = Ioss::FileControlOption::CONTROL_AUTO_MULTI_FILE; + auto observer = std::make_shared(i_region, params.elemFieldName, fileControlOption); + o_region.register_mesh_modification_observer(observer); + + o_region.set_file_cyclic_count(3); + run_topology_change(i_region, o_region, params); +} - auto fileControlOption = Ioss::FileControlOption::CONTROL_AUTO_MULTI_FILE; - auto observer = std::make_shared(i_region, elemFieldName, fileControlOption); - o_region.register_mesh_modification_observer(observer); +TEST(TestDynamicWrite, multi_file_cyclic_topology_modification) +{ + std::string outFile("cyclicMultiFileManyBlocks.g"); + std::string elemFieldName = "elem_field"; - run_simple_topology_change(i_region, o_region, observer, elemFieldName); + OutputParams params(outFile, elemFieldName); + + std::vector output_times{0.0 , 0.5 , 1.5 , 1.75, 2.0 , 3.0}; + std::vector output_steps{true , true, true , true, true, true }; + std::vector modification_steps{false, true, false, true, true, false}; + + params.set_data(output_times, output_steps, modification_steps); + + cleanup_cyclic_multi_files(outFile); + run_multi_file_cyclic_topology_change(params); + cleanup_cyclic_multi_files(outFile); +} + +void fill_group_gold_names(const int numFileGroups, + std::vector& gold_names, + std::vector& gold_full_names) +{ + gold_names.clear(); + gold_full_names.clear(); + + gold_names.push_back("/"); + gold_full_names.push_back("/"); + + for(int i=1; i<=numFileGroups; i++) { + std::ostringstream oss; + oss << Ioss::DynamicTopologyFileControl::group_prefix(); + oss << i; + + gold_names.push_back(oss.str()); + gold_full_names.push_back("/" + oss.str()); } +} - TEST(TestDynamicWrite, multi_file_simple_topology_modification) - { - std::string outFile("multiFileManyBlocks.g"); - std::string elemFieldName = "elem_field"; +void test_group_names(Ioss::DatabaseIO *database) +{ + Ioss::NameList names = database->groups_describe(false); + Ioss::NameList full_names = database->groups_describe(true); + + std::vector gold_names; + std::vector gold_full_names; + + fill_group_gold_names(database->num_child_group(), gold_names, gold_full_names); + + EXPECT_EQ(gold_names, names); + EXPECT_EQ(gold_full_names, full_names); +} + +void cleanup_single_file(const std::string &outFile) +{ + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + + std::string file1 = Ioss::Utils::decode_filename(outFile, util.parallel_rank(), util.parallel_size()); + unlink(file1.c_str()); +} + +void run_single_file_simple_topology_change(const OutputParams& params) +{ + Ioss::Init::Initializer io; + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + + int numBlocks = util.parallel_size(); + + std::string meshDesc = get_many_block_mesh_desc(numBlocks); + + Ioss::PropertyManager propertyManager; + + Ioss::DatabaseIO *i_database = Ioss::IOFactory::create("textmesh", meshDesc, Ioss::READ_MODEL, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region i_region(i_database, "input_model"); + EXPECT_TRUE(i_database != nullptr); + EXPECT_TRUE(i_database->ok(true)); + + propertyManager.add(Ioss::Property("ENABLE_FILE_GROUPS", 1)); + propertyManager.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_APPEND_GROUP)); + Ioss::DatabaseIO *o_database = Ioss::IOFactory::create("exodus", params.outFile, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region o_region(o_database, "output_model"); + EXPECT_TRUE(o_database != nullptr); + EXPECT_TRUE(o_database->ok(true)); + + auto fileControlOption = Ioss::FileControlOption::CONTROL_AUTO_SINGLE_FILE; + auto observer = std::make_shared(i_region, params.elemFieldName, fileControlOption); + o_region.register_mesh_modification_observer(observer); + + run_topology_change(i_region, o_region, params); + test_group_names(o_database); +} + +TEST(TestDynamicWrite, single_file_simple_topology_modification) +{ + std::string outFile("singleFileManyBlocks.g"); + std::string elemFieldName = "elem_field"; + + OutputParams params(outFile, elemFieldName); + + params.add(0.0, true, false) + .add(1.0, true, true) + .add(2.0, true, false) + .add(3.0, true, true) + .add(4.0, true, true) + .add(5.0, true, false); + + cleanup_single_file(outFile); + run_single_file_simple_topology_change(params); + cleanup_single_file(outFile); +} + +TEST(TestDynamicWrite, single_file_groups_not_enabled) +{ + Ioss::Init::Initializer io; + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + + int numBlocks = util.parallel_size(); + if(numBlocks > 1) GTEST_SKIP(); + + std::string meshDesc = "0,1,HEX_8,1,2,3,4,5,6,7,8,block_1" + "|coordinates:0,0,0,1,0,0,1,1,0,0,1,0,0,0,1,1,0,1,1,1,1,0,1,1"; + + Ioss::PropertyManager propertyManager; + + Ioss::DatabaseIO *i_database = Ioss::IOFactory::create("textmesh", meshDesc, Ioss::READ_MODEL, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region i_region(i_database, "input_model"); + EXPECT_TRUE(i_database != nullptr); + EXPECT_TRUE(i_database->ok(true)); + + std::string outFile("singleFileGroupsNotEnabled.g"); + std::string elemFieldName = "elem_field"; + cleanup_single_file(outFile); + + // Need the line below to allow this to pass + // propertyManager.add(Ioss::Property("ENABLE_FILE_GROUPS", 1)); + propertyManager.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_APPEND_GROUP)); + Ioss::DatabaseIO *o_database = Ioss::IOFactory::create("exodus", outFile, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region o_region(o_database, "output_model"); + EXPECT_TRUE(o_database != nullptr); + EXPECT_TRUE(o_database->ok(true)); + + auto fileControlOption = Ioss::FileControlOption::CONTROL_AUTO_SINGLE_FILE; + auto observer = std::make_shared(i_region, elemFieldName, fileControlOption); + EXPECT_THROW(o_region.register_mesh_modification_observer(observer), std::runtime_error); + cleanup_single_file(outFile); +} + +TEST(TestDynamicWrite, create_subgroup_with_file_reopen) +{ + std::string outFile("subgroupManyBlocks.g"); + std::string elemFieldName = "elem_field"; + + Ioss::Init::Initializer io; + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + + std::string file1 = Ioss::Utils::decode_filename(outFile, util.parallel_rank(), util.parallel_size()); + unlink(file1.c_str()); + + int numBlocks = util.parallel_size(); + if(numBlocks > 1) GTEST_SKIP(); + + std::string meshDesc = "0,1,HEX_8,1,2,3,4,5,6,7,8,block_1" + "|coordinates:0,0,0,1,0,0,1,1,0,0,1,0,0,0,1,1,0,1,1,1,1,0,1,1"; + + Ioss::PropertyManager propertyManager; + + Ioss::DatabaseIO *i_database = Ioss::IOFactory::create("textmesh", meshDesc, Ioss::READ_MODEL, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region i_region(i_database, "input_model"); + EXPECT_TRUE(i_database != nullptr); + EXPECT_TRUE(i_database->ok(true)); - cleanup_multi_files(outFile); - run_multi_file_simple_topology_change(elemFieldName, outFile); - cleanup_multi_files(outFile); + { + propertyManager.add(Ioss::Property("ENABLE_FILE_GROUPS", 1)); + Ioss::DatabaseIO *o_database = Ioss::IOFactory::create("exodus", outFile, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region o_region(o_database, "output_model"); + EXPECT_TRUE(o_database != nullptr); + EXPECT_TRUE(o_database->ok(true)); + o_database->create_subgroup("GROUP_1"); } - void cleanup_single_file(const std::string &outFile) { - Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + propertyManager.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_APPEND_GROUP)); + Ioss::DatabaseIO *o_database = Ioss::IOFactory::create("exodus", outFile, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region o_region(o_database, "output_model"); + EXPECT_TRUE(o_database != nullptr); + EXPECT_TRUE(o_database->ok(true)); + + // Group pointer is automatically at first child + o_database->create_subgroup("GROUP_2"); + + Ioss::NameList names = o_database->groups_describe(false); + Ioss::NameList full_names = o_database->groups_describe(true); - std::string file1 = - Ioss::Utils::decode_filename(outFile, util.parallel_rank(), util.parallel_size()); - unlink(file1.c_str()); + std::vector gold_names{"/", "GROUP_1", "GROUP_2"}; + std::vector gold_full_names{"/", "/GROUP_1", "/GROUP_1/GROUP_2"}; + + EXPECT_EQ(gold_names, names); + EXPECT_EQ(gold_full_names, full_names); } - void run_single_file_simple_topology_change(const std::string &elemFieldName, - const std::string &outFile) - { - Ioss::Init::Initializer io; - Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + unlink(file1.c_str()); +} + +TEST(TestDynamicWrite, create_subgroup_with_file_persistence_and_child_group) +{ + std::string outFile("subgroupManyBlocks.g"); + std::string elemFieldName = "elem_field"; - int numBlocks = util.parallel_size(); + Ioss::Init::Initializer io; + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); - std::string meshDesc = get_many_block_mesh_desc(numBlocks); + std::string file1 = Ioss::Utils::decode_filename(outFile, util.parallel_rank(), util.parallel_size()); + unlink(file1.c_str()); - Ioss::PropertyManager propertyManager; + int numBlocks = util.parallel_size(); + if(numBlocks > 1) GTEST_SKIP(); - Ioss::DatabaseIO *i_database = Ioss::IOFactory::create( - "textmesh", meshDesc, Ioss::READ_MODEL, Ioss::ParallelUtils::comm_world(), propertyManager); - Ioss::Region i_region(i_database, "input_model"); - EXPECT_TRUE(i_database != nullptr); - EXPECT_TRUE(i_database->ok(true)); + std::string meshDesc = "0,1,HEX_8,1,2,3,4,5,6,7,8,block_1" + "|coordinates:0,0,0,1,0,0,1,1,0,0,1,0,0,0,1,1,0,1,1,1,1,0,1,1"; + Ioss::PropertyManager propertyManager; + + Ioss::DatabaseIO *i_database = Ioss::IOFactory::create("textmesh", meshDesc, Ioss::READ_MODEL, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region i_region(i_database, "input_model"); + EXPECT_TRUE(i_database != nullptr); + EXPECT_TRUE(i_database->ok(true)); + + { propertyManager.add(Ioss::Property("ENABLE_FILE_GROUPS", 1)); propertyManager.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_APPEND_GROUP)); - Ioss::DatabaseIO *o_database = Ioss::IOFactory::create( - "exodus", outFile, Ioss::WRITE_RESULTS, Ioss::ParallelUtils::comm_world(), propertyManager); + Ioss::DatabaseIO *o_database = Ioss::IOFactory::create("exodus", outFile, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); Ioss::Region o_region(o_database, "output_model"); EXPECT_TRUE(o_database != nullptr); EXPECT_TRUE(o_database->ok(true)); - auto fileControlOption = Ioss::FileControlOption::CONTROL_AUTO_SINGLE_FILE; - auto observer = std::make_shared(i_region, elemFieldName, fileControlOption); - o_region.register_mesh_modification_observer(observer); + o_database->create_subgroup("GROUP_1"); - run_simple_topology_change(i_region, o_region, observer, elemFieldName); + // Group pointer is at "GROUP_1" ... "GROUP_2" is a child + o_database->create_subgroup("GROUP_2"); - Ioss::NameList names = o_database->groups_describe(false); + Ioss::NameList names = o_database->groups_describe(false); Ioss::NameList full_names = o_database->groups_describe(true); - std::vector gold_names{"/", "STEP-1", "STEP-2"}; - std::vector gold_full_names{"/", "/STEP-1", "/STEP-2"}; + std::vector gold_names{"/", "GROUP_1", "GROUP_2"}; + std::vector gold_full_names{"/", "/GROUP_1", "/GROUP_1/GROUP_2"}; EXPECT_EQ(gold_names, names); EXPECT_EQ(gold_full_names, full_names); } - TEST(TestDynamicWrite, single_file_simple_topology_modification) - { - std::string outFile("singleFileManyBlocks.g"); - std::string elemFieldName = "elem_field"; + unlink(file1.c_str()); +} - cleanup_single_file(outFile); - run_single_file_simple_topology_change(elemFieldName, outFile); - cleanup_single_file(outFile); - } +TEST(TestDynamicWrite, create_subgroup_with_file_persistence_and_no_child_group) +{ + std::string outFile("subgroupManyBlocks.g"); + std::string elemFieldName = "elem_field"; - TEST(TestDynamicWrite, single_file_groups_not_enabled) - { - Ioss::Init::Initializer io; - Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + Ioss::Init::Initializer io; + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); - int numBlocks = util.parallel_size(); - if (numBlocks > 1) - GTEST_SKIP(); + std::string file1 = Ioss::Utils::decode_filename(outFile, util.parallel_rank(), util.parallel_size()); + unlink(file1.c_str()); - std::string meshDesc = "0,1,HEX_8,1,2,3,4,5,6,7,8,block_1" - "|coordinates:0,0,0,1,0,0,1,1,0,0,1,0,0,0,1,1,0,1,1,1,1,0,1,1"; + int numBlocks = util.parallel_size(); + if(numBlocks > 1) GTEST_SKIP(); - Ioss::PropertyManager propertyManager; + std::string meshDesc = "0,1,HEX_8,1,2,3,4,5,6,7,8,block_1" + "|coordinates:0,0,0,1,0,0,1,1,0,0,1,0,0,0,1,1,0,1,1,1,1,0,1,1"; - Ioss::DatabaseIO *i_database = Ioss::IOFactory::create( - "textmesh", meshDesc, Ioss::READ_MODEL, Ioss::ParallelUtils::comm_world(), propertyManager); - Ioss::Region i_region(i_database, "input_model"); - EXPECT_TRUE(i_database != nullptr); - EXPECT_TRUE(i_database->ok(true)); + Ioss::PropertyManager propertyManager; - std::string outFile("singleFileGroupsNotEnabled.g"); - std::string elemFieldName = "elem_field"; - cleanup_single_file(outFile); + Ioss::DatabaseIO *i_database = Ioss::IOFactory::create("textmesh", meshDesc, Ioss::READ_MODEL, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region i_region(i_database, "input_model"); + EXPECT_TRUE(i_database != nullptr); + EXPECT_TRUE(i_database->ok(true)); - // Need the line below to allow this to pass - // propertyManager.add(Ioss::Property("ENABLE_FILE_GROUPS", 1)); + { + propertyManager.add(Ioss::Property("ENABLE_FILE_GROUPS", 1)); propertyManager.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_APPEND_GROUP)); - Ioss::DatabaseIO *o_database = Ioss::IOFactory::create( - "exodus", outFile, Ioss::WRITE_RESULTS, Ioss::ParallelUtils::comm_world(), propertyManager); + Ioss::DatabaseIO *o_database = Ioss::IOFactory::create("exodus", outFile, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); Ioss::Region o_region(o_database, "output_model"); EXPECT_TRUE(o_database != nullptr); EXPECT_TRUE(o_database->ok(true)); - auto fileControlOption = Ioss::FileControlOption::CONTROL_AUTO_SINGLE_FILE; - auto observer = std::make_shared(i_region, elemFieldName, fileControlOption); - EXPECT_THROW(o_region.register_mesh_modification_observer(observer), std::runtime_error); - cleanup_single_file(outFile); + o_database->create_subgroup("GROUP_1"); + + // Group pointer is reset to root group + EXPECT_TRUE(o_database->open_root_group()); + o_database->create_subgroup("GROUP_2"); + + Ioss::NameList names = o_database->groups_describe(false); + Ioss::NameList full_names = o_database->groups_describe(true); + + std::vector gold_names{"/", "GROUP_1", "GROUP_2"}; + std::vector gold_full_names{"/", "/GROUP_1", "/GROUP_2"}; + + EXPECT_EQ(gold_names, names); + EXPECT_EQ(gold_full_names, full_names); } - TEST(TestDynamicWrite, create_subgroup_with_file_reopen) - { - std::string outFile("subgroupManyBlocks.g"); - std::string elemFieldName = "elem_field"; - - Ioss::Init::Initializer io; - Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); - - std::string file1 = - Ioss::Utils::decode_filename(outFile, util.parallel_rank(), util.parallel_size()); - unlink(file1.c_str()); - - int numBlocks = util.parallel_size(); - if (numBlocks > 1) - GTEST_SKIP(); - - std::string meshDesc = "0,1,HEX_8,1,2,3,4,5,6,7,8,block_1" - "|coordinates:0,0,0,1,0,0,1,1,0,0,1,0,0,0,1,1,0,1,1,1,1,0,1,1"; - - Ioss::PropertyManager propertyManager; - - Ioss::DatabaseIO *i_database = Ioss::IOFactory::create( - "textmesh", meshDesc, Ioss::READ_MODEL, Ioss::ParallelUtils::comm_world(), propertyManager); - Ioss::Region i_region(i_database, "input_model"); - EXPECT_TRUE(i_database != nullptr); - EXPECT_TRUE(i_database->ok(true)); - - { - propertyManager.add(Ioss::Property("ENABLE_FILE_GROUPS", 1)); - Ioss::DatabaseIO *o_database = - Ioss::IOFactory::create("exodus", outFile, Ioss::WRITE_RESULTS, - Ioss::ParallelUtils::comm_world(), propertyManager); - Ioss::Region o_region(o_database, "output_model"); - EXPECT_TRUE(o_database != nullptr); - EXPECT_TRUE(o_database->ok(true)); - o_database->create_subgroup("GROUP_1"); - } + unlink(file1.c_str()); +} + + +void run_topology_change_with_multiple_output(const Ioss::Region& i_region, + Ioss::Region &o_region1, + Ioss::Region &o_region2, + const OutputParams& params1, + const OutputParams& params2) +{ + ASSERT_EQ(params1.modification_steps, params2.modification_steps); + + auto observer1 = o_region1.get_mesh_modification_observer(); + auto observer2 = o_region2.get_mesh_modification_observer(); + + define_model(i_region, o_region1); + write_model(i_region, o_region1); + define_transient(i_region, o_region1, params1.elemFieldName); + + define_model(i_region, o_region2); + write_model(i_region, o_region2); + define_transient(i_region, o_region2, params2.elemFieldName); + + auto numSteps = params1.output_steps.size(); - { - propertyManager.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_APPEND_GROUP)); - Ioss::DatabaseIO *o_database = - Ioss::IOFactory::create("exodus", outFile, Ioss::WRITE_RESULTS, - Ioss::ParallelUtils::comm_world(), propertyManager); - Ioss::Region o_region(o_database, "output_model"); - EXPECT_TRUE(o_database != nullptr); - EXPECT_TRUE(o_database->ok(true)); + int maxStep1 = 1; + int maxStep2 = 1; - // Group pointer is still at root level - o_database->create_subgroup("GROUP_2"); + double minTime1 = numSteps > 0 ? params1.output_times[0] : 0.0; + double minTime2 = numSteps > 0 ? params2.output_times[0] : 0.0; - Ioss::NameList names = o_database->groups_describe(false); - Ioss::NameList full_names = o_database->groups_describe(true); + bool doneOutputAfterModification1 = true; + bool doneOutputAfterModification2 = true; - std::vector gold_names{"/", "GROUP_1", "GROUP_2"}; - std::vector gold_full_names{"/", "/GROUP_1", "/GROUP_2"}; + for(size_t i=0; iset_topology_modification(Ioss::TOPOLOGY_UNKNOWN); + maxStep1 = 1; + maxStep2 = 1; + + EXPECT_EQ(Ioss::TOPOLOGY_UNKNOWN, observer1->get_topology_modification()); + EXPECT_EQ(Ioss::TOPOLOGY_UNKNOWN, observer2->get_topology_modification()); - EXPECT_EQ(gold_names, names); - EXPECT_EQ(gold_full_names, full_names); + doneOutputAfterModification1 = false; + doneOutputAfterModification2 = false; } - unlink(file1.c_str()); + do_output(o_region1, params1, i, minTime1, maxStep1, doneOutputAfterModification1); + do_output(o_region2, params2, i, minTime2, maxStep2, doneOutputAfterModification2); } +} + +void run_single_file_simple_topology_change_with_multiple_output(const std::string& model, + const OutputParams& params1, + const OutputParams& params2) +{ + Ioss::Init::Initializer io; + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + + auto broker = Ioss::DynamicTopologyBroker::broker(); + broker->register_model(model); + + int numBlocks = util.parallel_size(); + + std::string meshDesc = get_many_block_mesh_desc(numBlocks); + + Ioss::PropertyManager propertyManager; + + Ioss::DatabaseIO *i_database = Ioss::IOFactory::create("textmesh", meshDesc, Ioss::READ_MODEL, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region i_region(i_database, "input_model"); + EXPECT_TRUE(i_database != nullptr); + EXPECT_TRUE(i_database->ok(true)); + + propertyManager.add(Ioss::Property("ENABLE_FILE_GROUPS", 1)); + propertyManager.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_APPEND_GROUP)); + + Ioss::DatabaseIO *o_database1 = Ioss::IOFactory::create("exodus", params1.outFile, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region o_region1(o_database1, "region1"); + EXPECT_TRUE(o_database1 != nullptr); + EXPECT_TRUE(o_database1->ok(true)); + + auto fileControlOption = Ioss::FileControlOption::CONTROL_AUTO_SINGLE_FILE; + auto observer1 = std::make_shared(i_region, params1.elemFieldName, fileControlOption); + broker->register_observer(model, observer1, o_region1); + + Ioss::DatabaseIO *o_database2 = Ioss::IOFactory::create("exodus", params2.outFile, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region o_region2(o_database2, "region2"); + EXPECT_TRUE(o_database2 != nullptr); + EXPECT_TRUE(o_database2->ok(true)); + + auto observer2 = std::make_shared(i_region, params2.elemFieldName, fileControlOption); + broker->register_observer(model, observer2, o_region2); + + run_topology_change_with_multiple_output(i_region, o_region1, o_region2, params1, params2); + + test_group_names(o_database1); + test_group_names(o_database2); +} + +TEST(TestDynamicWrite, single_file_simple_topology_modification_with_multiple_output) +{ + std::string outFile1("singleFileManyBlocks1.g"); + std::string outFile2("singleFileManyBlocks2.g"); + std::string elemFieldName = "elem_field"; + std::string model = "multiple-output"; + + OutputParams params1(outFile1, elemFieldName); + + params1.add(0.0, true , false) + .add(1.0, true , true) + .add(2.0, false, false) + .add(3.0, true , true) + .add(4.0, true , false) + .add(5.0, true , true); + + OutputParams params2(outFile2, elemFieldName); + + params2.add(0.0, true , false) + .add(1.0, true , true) + .add(2.0, true , false) + .add(3.0, false, true) + .add(4.0, true , false) + .add(5.0, true , true); + + cleanup_single_file(outFile1); + cleanup_single_file(outFile2); + run_single_file_simple_topology_change_with_multiple_output(model, params1, params2); + cleanup_single_file(outFile1); + cleanup_single_file(outFile2); +} + +TEST(TestDynamicWrite, same_model_triggers_same_modification_for_all_observers) +{ + Ioss::Init::Initializer io; + Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + + std::string outFile1("sameModelManyBlocks1.g"); + std::string outFile2("sameModelManyBlocks2.g"); + std::string elemFieldName("elem_field"); + std::string model("same-model"); + + auto broker = Ioss::DynamicTopologyBroker::broker(); + broker->register_model(model); + + std::string file1 = Ioss::Utils::decode_filename(outFile1, util.parallel_rank(), util.parallel_size()); + std::string file2 = Ioss::Utils::decode_filename(outFile2, util.parallel_rank(), util.parallel_size()); + + unlink(file1.c_str()); + unlink(file2.c_str()); + + int numBlocks = util.parallel_size(); + if(numBlocks > 1) GTEST_SKIP(); + + std::string meshDesc = get_many_block_mesh_desc(numBlocks); + + Ioss::PropertyManager propertyManager; + + Ioss::DatabaseIO *i_database = Ioss::IOFactory::create("textmesh", meshDesc, Ioss::READ_MODEL, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region i_region(i_database, "input_model"); + EXPECT_TRUE(i_database != nullptr); + EXPECT_TRUE(i_database->ok(true)); - TEST(TestDynamicWrite, create_subgroup_with_file_persistence_and_child_group) { - std::string outFile("subgroupManyBlocks.g"); - std::string elemFieldName = "elem_field"; + propertyManager.add(Ioss::Property("ENABLE_FILE_GROUPS", 1)); + propertyManager.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_APPEND_GROUP)); - Ioss::Init::Initializer io; - Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + Ioss::DatabaseIO *o_database1 = Ioss::IOFactory::create("exodus", outFile1, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region o_region1(o_database1, "region1"); + EXPECT_TRUE(o_database1 != nullptr); + EXPECT_TRUE(o_database1->ok(true)); - std::string file1 = - Ioss::Utils::decode_filename(outFile, util.parallel_rank(), util.parallel_size()); - unlink(file1.c_str()); + auto fileControlOption = Ioss::FileControlOption::CONTROL_AUTO_SINGLE_FILE; + auto observer1 = std::make_shared(i_region, elemFieldName, fileControlOption); + broker->register_observer(model, observer1, o_region1); - int numBlocks = util.parallel_size(); - if (numBlocks > 1) - GTEST_SKIP(); + Ioss::DatabaseIO *o_database2 = Ioss::IOFactory::create("exodus", outFile2, Ioss::WRITE_RESULTS, + Ioss::ParallelUtils::comm_world(), + propertyManager); + Ioss::Region o_region2(o_database2, "region2"); + EXPECT_TRUE(o_database2 != nullptr); + EXPECT_TRUE(o_database2->ok(true)); - std::string meshDesc = "0,1,HEX_8,1,2,3,4,5,6,7,8,block_1" - "|coordinates:0,0,0,1,0,0,1,1,0,0,1,0,0,0,1,1,0,1,1,1,1,0,1,1"; + auto observer2 = std::make_shared(i_region, elemFieldName, fileControlOption); + broker->register_observer(model, observer2, o_region2); - Ioss::PropertyManager propertyManager; + EXPECT_EQ(Ioss::TOPOLOGY_SAME, observer1->get_topology_modification()); + EXPECT_EQ(Ioss::TOPOLOGY_SAME, observer2->get_topology_modification()); - Ioss::DatabaseIO *i_database = Ioss::IOFactory::create( - "textmesh", meshDesc, Ioss::READ_MODEL, Ioss::ParallelUtils::comm_world(), propertyManager); - Ioss::Region i_region(i_database, "input_model"); - EXPECT_TRUE(i_database != nullptr); - EXPECT_TRUE(i_database->ok(true)); + observer1->set_topology_modification(Ioss::TOPOLOGY_UNKNOWN); - { - propertyManager.add(Ioss::Property("ENABLE_FILE_GROUPS", 1)); - propertyManager.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_APPEND_GROUP)); - Ioss::DatabaseIO *o_database = - Ioss::IOFactory::create("exodus", outFile, Ioss::WRITE_RESULTS, - Ioss::ParallelUtils::comm_world(), propertyManager); - Ioss::Region o_region(o_database, "output_model"); - EXPECT_TRUE(o_database != nullptr); - EXPECT_TRUE(o_database->ok(true)); + EXPECT_EQ(Ioss::TOPOLOGY_UNKNOWN, observer1->get_topology_modification()); + EXPECT_EQ(Ioss::TOPOLOGY_UNKNOWN, observer2->get_topology_modification()); + } - o_database->create_subgroup("GROUP_1"); + unlink(file1.c_str()); + unlink(file2.c_str()); +} - // Group pointer is at "GROUP_1" ... "GROUP_2" is a child - o_database->create_subgroup("GROUP_2"); +void test_single_file_simple_topology_change_data(Ioss::Region& i_region, const std::string& elemFieldName, + int gold_step, double gold_time) +{ + i_region.begin_state(gold_step); + for (Ioss::ElementBlock *i_eb : i_region.get_element_blocks()) { + size_t num_elem = i_eb->get_property("entity_count").get_int(); - Ioss::NameList names = o_database->groups_describe(false); - Ioss::NameList full_names = o_database->groups_describe(true); + std::vector field_data(num_elem); + std::vector elem_ids; - std::vector gold_names{"/", "GROUP_1", "GROUP_2"}; - std::vector gold_full_names{"/", "/GROUP_1", "/GROUP_1/GROUP_2"}; + i_eb->get_field_data(elemFieldName, field_data); + i_eb->get_field_data("ids", elem_ids); - EXPECT_EQ(gold_names, names); - EXPECT_EQ(gold_full_names, full_names); + for (size_t i = 0; i < elem_ids.size(); i++) { + double gold_value = (double)elem_ids[i] + 100*gold_time; + EXPECT_NEAR(gold_value, field_data[i], 1.0e-6); } - - unlink(file1.c_str()); } +} - TEST(TestDynamicWrite, create_subgroup_with_file_persistence_and_no_child_group) - { - std::string outFile("subgroupManyBlocks.g"); - std::string elemFieldName = "elem_field"; +void read_and_test_single_file_simple_topology_change(const OutputParams& params) +{ + Ioss::PropertyManager propertyManager; - Ioss::Init::Initializer io; - Ioss::ParallelUtils util(Ioss::ParallelUtils::comm_world()); + Ioss::DatabaseIO *i_database = Ioss::IOFactory::create("exodus", params.outFile, Ioss::READ_RESTART, + Ioss::ParallelUtils::comm_world(), + propertyManager); - std::string file1 = - Ioss::Utils::decode_filename(outFile, util.parallel_rank(), util.parallel_size()); - unlink(file1.c_str()); + test_group_names(i_database); - int numBlocks = util.parallel_size(); - if (numBlocks > 1) - GTEST_SKIP(); + Ioss::Region i_region(i_database, "input_model"); + EXPECT_TRUE(i_database != nullptr); + EXPECT_TRUE(i_database->ok(true)); - std::string meshDesc = "0,1,HEX_8,1,2,3,4,5,6,7,8,block_1" - "|coordinates:0,0,0,1,0,0,1,1,0,0,1,0,0,0,1,1,0,1,1,1,1,0,1,1"; + auto numSteps = params.output_steps.size(); - Ioss::PropertyManager propertyManager; + int numMods = 0; - Ioss::DatabaseIO *i_database = Ioss::IOFactory::create( - "textmesh", meshDesc, Ioss::READ_MODEL, Ioss::ParallelUtils::comm_world(), propertyManager); - Ioss::Region i_region(i_database, "input_model"); - EXPECT_TRUE(i_database != nullptr); - EXPECT_TRUE(i_database->ok(true)); + int maxStep = 1; - { - propertyManager.add(Ioss::Property("ENABLE_FILE_GROUPS", 1)); - propertyManager.add(Ioss::Property("APPEND_OUTPUT", Ioss::DB_APPEND_GROUP)); - Ioss::DatabaseIO *o_database = - Ioss::IOFactory::create("exodus", outFile, Ioss::WRITE_RESULTS, - Ioss::ParallelUtils::comm_world(), propertyManager); - Ioss::Region o_region(o_database, "output_model"); - EXPECT_TRUE(o_database != nullptr); - EXPECT_TRUE(o_database->ok(true)); + double minTime = numSteps > 0 ? params.output_times[0] : 0.0; + double maxTime = numSteps > 0 ? params.output_times[0] : 0.0; - o_database->create_subgroup("GROUP_1"); + bool doneOutputAfterModification = true; - // Group pointer is reset to root group - EXPECT_TRUE(o_database->open_root_group()); - o_database->create_subgroup("GROUP_2"); + Ioss::NameList names = i_database->groups_describe(false); - Ioss::NameList names = o_database->groups_describe(false); - Ioss::NameList full_names = o_database->groups_describe(true); + for(size_t i=0; i gold_names{"/", "GROUP_1", "GROUP_2"}; - std::vector gold_full_names{"/", "/GROUP_1", "/GROUP_2"}; + for(size_t j=i+1; jget_property("entity_count").get_int(); + EXPECT_TRUE(i_region.load_group_mesh(names[numMods+1])); - std::vector field_data(num_elem); - std::vector elem_ids; + doneOutputAfterModification = false; + } + + if(params.output_steps[i]) { + if(!doneOutputAfterModification) { + minTime = params.output_times[i]; + } + auto min_result = i_region.get_min_time(); + EXPECT_EQ(1, min_result.first); + EXPECT_NEAR(minTime, min_result.second, 1.0e-6); + test_single_file_simple_topology_change_data(i_region, params.elemFieldName, 1, minTime); - i_eb->get_field_data(elemFieldName, field_data); - i_eb->get_field_data("ids", elem_ids); + if((((i+1) < numSteps) && params.modification_steps[i+1]) || (i == (numSteps-1))) { + auto max_result = i_region.get_max_time(); + EXPECT_EQ(maxStep, max_result.first); + EXPECT_NEAR(maxTime, max_result.second, 1.0e-6); - for (size_t i = 0; i < elem_ids.size(); i++) { - double gold_value = (double)elem_ids[i] + 100 * gold_time; - EXPECT_NEAR(gold_value, field_data[i], 1.0e-6); + test_single_file_simple_topology_change_data(i_region, params.elemFieldName, maxStep, maxTime); } + + maxStep++; + doneOutputAfterModification = true; } } +} - void read_and_test_single_file_simple_topology_change(const std::string &elemFieldName, - const std::string &outFile) - { - Ioss::PropertyManager propertyManager; +TEST(TestDynamicRead, single_file_simple_topology_modification) +{ + std::string outFile("singleFileManyBlocks.g"); + std::string elemFieldName = "elem_field"; - Ioss::DatabaseIO *i_database = Ioss::IOFactory::create( - "exodus", outFile, Ioss::READ_RESTART, Ioss::ParallelUtils::comm_world(), propertyManager); + OutputParams params(outFile, elemFieldName); - Ioss::NameList names = i_database->groups_describe(false); - Ioss::NameList full_names = i_database->groups_describe(true); + params.add(0.0, true, false) + .add(1.0, true, true) + .add(2.0, true, false) + .add(3.0, true, true) + .add(4.0, true, true) + .add(5.0, true, false); - std::vector gold_names{"/", "STEP-1", "STEP-2"}; - std::vector gold_full_names{"/", "/STEP-1", "/STEP-2"}; + cleanup_single_file(outFile); + run_single_file_simple_topology_change(params); + read_and_test_single_file_simple_topology_change(params); + cleanup_single_file(outFile); +} - EXPECT_EQ(gold_names, names); - EXPECT_EQ(gold_full_names, full_names); +} - EXPECT_TRUE(i_database->open_group("STEP-1")); - - Ioss::Region i_region(i_database, "input_model"); - EXPECT_TRUE(i_database != nullptr); - EXPECT_TRUE(i_database->ok(true)); - - double gold_time = 0.0; - int gold_step = 1; - auto min_result1 = i_region.get_min_time(); - EXPECT_EQ(gold_step, min_result1.first); - EXPECT_NEAR(gold_time, min_result1.second, 1.0e-6); - - auto max_result1 = i_region.get_max_time(); - EXPECT_EQ(gold_step, max_result1.first); - EXPECT_NEAR(gold_time, max_result1.second, 1.0e-6); - test_single_file_simple_topology_change_data(i_region, elemFieldName, gold_step, gold_time); - - EXPECT_TRUE(i_region.load_group_mesh("STEP-2")); - - double gold_min_time = 1.0; - int gold_min_step = 1; - auto min_result2 = i_region.get_min_time(); - EXPECT_EQ(gold_min_step, min_result2.first); - EXPECT_NEAR(gold_min_time, min_result2.second, 1.0e-6); - test_single_file_simple_topology_change_data(i_region, elemFieldName, gold_min_step, - gold_min_time); - - auto max_result2 = i_region.get_max_time(); - double gold_max_time = 2.0; - int gold_max_step = 2; - EXPECT_EQ(gold_max_step, max_result2.first); - EXPECT_NEAR(gold_max_time, max_result2.second, 1.0e-6); - test_single_file_simple_topology_change_data(i_region, elemFieldName, gold_max_step, - gold_max_time); - } - - TEST(TestDynamicRead, single_file_simple_topology_modification) - { - std::string outFile("singleFileManyBlocks.g"); - std::string elemFieldName = "elem_field"; - - cleanup_single_file(outFile); - run_single_file_simple_topology_change(elemFieldName, outFile); - read_and_test_single_file_simple_topology_change(elemFieldName, outFile); - cleanup_single_file(outFile); - } -} // namespace