[Input] Use full AnyMap sorting algorithm in all cases

The simpler sorting algorithm used in Python did not account for the
additional ordering rules allowed by the AnyMap class.
This commit is contained in:
Ray Speth
2021-04-11 19:57:40 -04:00
committed by Ingmar Schoegl
parent 8b8bf4b12b
commit df262a0f92
4 changed files with 155 additions and 89 deletions

View File

@@ -528,6 +528,55 @@ public:
return Iterator(m_data.end(), m_data.end());
}
class OrderedIterator;
//! Proxy for iterating over an AnyMap in the defined output ordering.
//! See ordered().
class OrderedProxy {
public:
OrderedProxy() {}
OrderedProxy(const AnyMap& data);
OrderedIterator begin() const;
OrderedIterator end() const;
typedef std::vector<std::pair<
std::pair<int, int>,
const std::pair<const std::string, AnyValue>*>> OrderVector;
private:
const AnyMap* m_data;
OrderVector m_ordered;
std::unique_ptr<std::pair<const std::string, AnyValue>> m_units;
};
//! Defined to allow the OrderedProxy class to be used with range-based
//! for loops.
class OrderedIterator {
public:
OrderedIterator() {}
OrderedIterator(const OrderedProxy::OrderVector::const_iterator& start,
const OrderedProxy::OrderVector::const_iterator& stop);
const std::pair<const std::string, AnyValue>& operator*() const {
return *m_iter->second;
}
const std::pair<const std::string, AnyValue>* operator->() const {
return &(*m_iter->second);
}
bool operator!=(const OrderedIterator& right) const {
return m_iter != right.m_iter;
}
OrderedIterator& operator++() { ++m_iter; return *this; }
private:
OrderedProxy::OrderVector::const_iterator m_iter;
OrderedProxy::OrderVector::const_iterator m_stop;
};
// Return a proxy object that allows iteration in an order determined by the
// order of insertion, the location in an input file, and rules specified by
// the addOrderingRules() method.
OrderedProxy ordered() const { return OrderedProxy(*this); }
//! Returns the number of elements in this map
size_t size() const {
return m_data.size();

View File

@@ -58,9 +58,19 @@ cdef extern from "cantera/base/AnyMap.h" namespace "Cantera":
Iterator& operator++()
cbool operator!=(Iterator&)
cppclass OrderedIterator:
pair[string, CxxAnyValue]& operator*()
OrderedIterator& operator++()
cbool operator!=(OrderedIterator&)
cppclass OrderedProxy:
OrderedIterator begin()
OrderedIterator end()
CxxAnyMap()
Iterator begin()
Iterator end()
OrderedProxy ordered() except +translate_exception
CxxAnyValue& operator[](string) except +translate_exception
cbool hasKey(string)
string keys_str()
@@ -74,7 +84,6 @@ cdef extern from "cantera/base/AnyMap.h" namespace "Cantera":
string type_str()
cbool isType "is" [T]()
cbool isScalar()
pair[int, int] order()
CxxAnyMap AnyMapFromYamlFile "Cantera::AnyMap::fromYamlFile" (string) except +translate_exception
CxxAnyMap AnyMapFromYamlString "Cantera::AnyMap::fromYamlString" (string) except +translate_exception

View File

@@ -116,14 +116,9 @@ cdef anyvalueToPython(string name, CxxAnyValue& v):
cdef anymapToPython(CxxAnyMap& m):
py_items = []
m.applyUnits()
for item in m:
py_items.append((item.second.order(),
pystr(item.first),
anyvalueToPython(item.first, item.second)))
py_items.sort()
return {key: value for (_, key, value) in py_items}
return {pystr(item.first): anyvalueToPython(item.first, item.second)
for item in m.ordered()}
cdef mergeAnyMap(CxxAnyMap& primary, CxxAnyMap& extra):
@@ -134,16 +129,9 @@ cdef mergeAnyMap(CxxAnyMap& primary, CxxAnyMap& extra):
Used to combine generated data representing the current state of the object
(primary) with user-supplied fields (extra) not directly used by Cantera.
"""
py_items = []
for item in primary:
py_items.append((item.second.order(),
pystr(item.first),
anyvalueToPython(item.first, item.second)))
out = {pystr(item.first): anyvalueToPython(item.first, item.second)
for item in primary.ordered()}
for item in extra:
if not primary.hasKey(item.first):
py_items.append((item.second.order(),
pystr(item.first),
anyvalueToPython(item.first, item.second)))
py_items.sort()
return {key: value for (_, key, value) in py_items}
out[pystr(item.first)] = anyvalueToPython(item.first, item.second)
return out

View File

@@ -249,78 +249,14 @@ struct convert<Cantera::AnyMap> {
YAML::Emitter& operator<<(YAML::Emitter& out, const AnyMap& rhs)
{
// Initial sort based on the order in which items are added
vector<std::tuple<std::pair<int, int>, std::string, const AnyValue*>> ordered;
// Units always come first
AnyValue units;
if (rhs.hasKey("__units__") && rhs["__units__"].as<AnyMap>().size()) {
units = rhs["__units__"];
units.setFlowStyle();
ordered.emplace_back(std::pair<int, int>{-2, 0}, std::string("units"), &units);
}
int head = 0; // sort key of the first programmatically-added item
int tail = 0; // sort key of the last programmatically-added item
for (const auto& item : rhs) {
const auto& order = item.second.order();
if (order.first == -1) { // Item is not from an input file
head = std::min(head, order.second);
tail = std::max(tail, order.second);
}
ordered.emplace_back(order, item.first, &item.second);
}
std::sort(ordered.begin(), ordered.end());
// Adjust sort keys for items that should moved to the beginning or end of
// the list
if (rhs.hasKey("__type__")) {
bool order_changed = false;
const auto& itemType = rhs["__type__"].asString();
std::unique_lock<std::mutex> lock(yaml_field_order_mutex);
if (AnyMap::s_headFields.count(itemType)) {
for (const auto& key : AnyMap::s_headFields[itemType]) {
for (auto& item : ordered) {
if (std::get<0>(item).first >= 0) {
// This and following items come from an input file and
// should not be re-ordered
break;
}
if (std::get<1>(item) == key) {
std::get<0>(item).second = --head;
order_changed = true;
}
}
}
}
if (AnyMap::s_tailFields.count(itemType)) {
for (const auto& key : AnyMap::s_tailFields[itemType]) {
for (auto& item : ordered) {
if (std::get<0>(item).first >= 0) {
// This and following items come from an input file and
// should not be re-ordered
break;
}
if (std::get<1>(item) == key) {
std::get<0>(item).second = ++tail;
order_changed = true;
}
}
}
}
if (order_changed) {
std::sort(ordered.begin(), ordered.end());
}
}
bool flow = rhs.getBool("__flow__", false);
if (flow) {
out << YAML::Flow;
out << YAML::BeginMap;
size_t width = 15;
for (const auto& item : ordered) {
const auto& name = std::get<1>(item);
const auto& value = *std::get<2>(item);
for (const auto& item : rhs.ordered()) {
const auto& name = item.first;
const auto& value = item.second;
string valueStr;
bool foundType = true;
if (value.is<double>()) {
@@ -354,9 +290,9 @@ YAML::Emitter& operator<<(YAML::Emitter& out, const AnyMap& rhs)
}
} else {
out << YAML::BeginMap;
for (const auto& item : ordered) {
out << std::get<1>(item);
out << *std::get<2>(item);
for (const auto& item : rhs.ordered()) {
out << item.first;
out << item.second;
}
}
out << YAML::EndMap;
@@ -1526,6 +1462,90 @@ AnyMap::Iterator& AnyMap::Iterator::operator++()
return *this;
}
AnyMap::OrderedProxy::OrderedProxy(const AnyMap& data)
: m_data(&data)
{
// Units always come first
if (m_data->hasKey("__units__") && m_data->at("__units__").as<AnyMap>().size()) {
m_units.reset(new std::pair<const string, AnyValue>{"units", m_data->at("__units__")});
m_units->second.setFlowStyle();
m_ordered.emplace_back(std::pair<int, int>{-2, 0}, m_units.get());
}
int head = 0; // sort key of the first programmatically-added item
int tail = 0; // sort key of the last programmatically-added item
for (auto& item : *m_data) {
const auto& order = item.second.order();
if (order.first == -1) { // Item is not from an input file
head = std::min(head, order.second);
tail = std::max(tail, order.second);
}
m_ordered.emplace_back(order, &item);
}
std::sort(m_ordered.begin(), m_ordered.end());
// Adjust sort keys for items that should moved to the beginning or end of
// the list
if (m_data->hasKey("__type__")) {
bool order_changed = false;
const auto& itemType = m_data->at("__type__").asString();
std::unique_lock<std::mutex> lock(yaml_field_order_mutex);
if (AnyMap::s_headFields.count(itemType)) {
for (const auto& key : AnyMap::s_headFields[itemType]) {
for (auto& item : m_ordered) {
if (item.first.first >= 0) {
// This and following items come from an input file and
// should not be re-ordered
break;
}
if (item.second->first == key) {
item.first.second = --head;
order_changed = true;
}
}
}
}
if (AnyMap::s_tailFields.count(itemType)) {
for (const auto& key : AnyMap::s_tailFields[itemType]) {
for (auto& item : m_ordered) {
if (item.first.first >= 0) {
// This and following items come from an input file and
// should not be re-ordered
break;
}
if (item.second->first == key) {
item.first.second = ++tail;
order_changed = true;
}
}
}
}
if (order_changed) {
std::sort(m_ordered.begin(), m_ordered.end());
}
}
}
AnyMap::OrderedIterator AnyMap::OrderedProxy::begin() const
{
return OrderedIterator(m_ordered.begin(), m_ordered.end());
}
AnyMap::OrderedIterator AnyMap::OrderedProxy::end() const
{
return OrderedIterator(m_ordered.end(), m_ordered.end());
}
AnyMap::OrderedIterator::OrderedIterator(
const AnyMap::OrderedProxy::OrderVector::const_iterator& start,
const AnyMap::OrderedProxy::OrderVector::const_iterator& stop)
{
m_iter = start;
m_stop = stop;
}
bool AnyMap::operator==(const AnyMap& other) const
{
// First, make sure that 'other' has all of the non-hidden keys that are in