Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add infix string builder #1121

Merged
merged 4 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/orange/orangeinp/CsgTreeUtils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "corecel/cont/Range.hh"

#include "detail/InfixStringBuilder.hh"
#include "detail/NodeReplacementInserter.hh"
#include "detail/PostfixLogicBuilder.hh"

Expand Down Expand Up @@ -106,7 +107,7 @@ void simplify(CsgTree* tree, NodeId start)
/*!
* Convert a node to postfix notation.
*/
std::vector<LocalSurfaceId::size_type>
[[nodiscard]] std::vector<LocalSurfaceId::size_type>
build_postfix(CsgTree const& tree, NodeId n)
{
CELER_EXPECT(n < tree.size());
Expand All @@ -117,6 +118,20 @@ build_postfix(CsgTree const& tree, NodeId n)
return result;
}

//---------------------------------------------------------------------------//
/*!
* Convert a node to an infix string expression.
*/
[[nodiscard]] std::string build_infix_string(CsgTree const& tree, NodeId n)
{
CELER_EXPECT(n < tree.size());
std::ostringstream os;
detail::InfixStringBuilder build_impl{tree, &os};

build_impl(n);
return os.str();
}

//---------------------------------------------------------------------------//
/*!
* Construct the sorted set of all surfaces that are part of the tree.
Expand All @@ -125,7 +140,7 @@ build_postfix(CsgTree const& tree, NodeId n)
* Thanks to the CSG tree's deduplication, each surface should appear in the
* tree at most once.
*/
std::vector<LocalSurfaceId> calc_surfaces(CsgTree const& tree)
[[nodiscard]] std::vector<LocalSurfaceId> calc_surfaces(CsgTree const& tree)
{
std::vector<LocalSurfaceId> result;
for (auto node_id : range(NodeId{tree.size()}))
Expand Down
8 changes: 6 additions & 2 deletions src/orange/orangeinp/CsgTreeUtils.hh
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,15 @@ orangeinp::NodeId simplify_up(CsgTree* tree, orangeinp::NodeId start);
void simplify(CsgTree* tree, orangeinp::NodeId start);

// Convert a node to postfix notation
std::vector<LocalSurfaceId::size_type>
[[nodiscard]] std::vector<LocalSurfaceId::size_type>
build_postfix(CsgTree const& tree, orangeinp::NodeId n);

// Transform a CSG node into a string expression
[[nodiscard]] std::string
build_infix_string(CsgTree const& tree, orangeinp::NodeId n);

// Get the set of unsimplified surfaces in a tree
std::vector<LocalSurfaceId> calc_surfaces(CsgTree const& tree);
[[nodiscard]] std::vector<LocalSurfaceId> calc_surfaces(CsgTree const& tree);

//---------------------------------------------------------------------------//
} // namespace orangeinp
Expand Down
182 changes: 182 additions & 0 deletions src/orange/orangeinp/detail/InfixStringBuilder.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
//----------------------------------*-C++-*----------------------------------//
// Copyright 2024 UT-Battelle, LLC, and other Celeritas developers.
// See the top-level COPYRIGHT file for details.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
//---------------------------------------------------------------------------//
//! \file orange/orangeinp/detail/InfixStringBuilder.hh
//---------------------------------------------------------------------------//
#pragma once

#include <ostream>

#include "corecel/cont/VariantUtils.hh"

#include "../CsgTree.hh"

namespace celeritas
{
namespace orangeinp
{
namespace detail
{
//---------------------------------------------------------------------------//
/*!
* Transform a CSG node into a string expression.
*
* The string will be a combination of:
* - an \c any function for a union of all listed components
* - an \c all function for an intersection of all listed components
* - a \c ! negation operator applied to the left of an operation or other
* negation
* - a surface ID preceded by a \c - or \c + indicating "inside" or "outside",
* respectively.
*
* Example of a cylindrical shell: \verbatim
all(all(+0, -1, -3), !all(+0, -1, -2))
* \endverbatim
*/
class InfixStringBuilder
{
public:
// Construct with tree and a stream to write to
explicit inline InfixStringBuilder(CsgTree const& tree, std::ostream* os);

//! Build from a node ID
inline void operator()(NodeId const& n);

//!@{
//! \name Visit a node directly
// Append 'true'
inline void operator()(True const&);
// False is never explicitly part of the node tree
inline void operator()(False const&);
// Append a surface ID
inline void operator()(Surface const&);
// Aliased nodes should never be reachable explicitly
inline void operator()(Aliased const&);
// Visit a negated node and append 'not'
inline void operator()(Negated const&);
// Visit daughter nodes and append the conjunction.
inline void operator()(Joined const&);
//!@}

private:
ContainerVisitor<CsgTree const&, NodeId> visit_node_;
std::ostream* os_;
bool negated_{false};
};

//---------------------------------------------------------------------------//
// INLINE DEFINITIONS
//---------------------------------------------------------------------------//
/*!
* Construct with a reference to an output stream.
*/
InfixStringBuilder::InfixStringBuilder(CsgTree const& tree, std::ostream* os)
: visit_node_{tree}, os_{os}
{
CELER_EXPECT(os_);
}

//---------------------------------------------------------------------------//
/*!
* Build from a node ID.
*/
void InfixStringBuilder::operator()(NodeId const& n)
{
visit_node_(*this, n);
}

//---------------------------------------------------------------------------//
/*!
* Append the "true" token.
*/
void InfixStringBuilder::operator()(True const&)
{
*os_ << (negated_ ? 'F' : 'T');
negated_ = false;
}

//---------------------------------------------------------------------------//
/*!
* Explicit "False" should never be possible for a CSG cell.
*
* The 'false' standin is always aliased to "not true" in the CSG tree.
*/
void InfixStringBuilder::operator()(False const&)
{
CELER_ASSERT_UNREACHABLE();
}

//---------------------------------------------------------------------------//
/*!
* Push a surface ID.
*/
void InfixStringBuilder::operator()(Surface const& s)
{
CELER_EXPECT(s.id < logic::lbegin);

static_assert(to_sense(true) == Sense::outside);
*os_ << (negated_ ? '-' : '+') << s.id.unchecked_get();
negated_ = false;
}

//---------------------------------------------------------------------------//
/*!
* Push an aliased node.
*
* Note: aliased node won't be reachable if a tree is fully simplified, *but* a
* node can be printed for testing before it's simplified.
*/
void InfixStringBuilder::operator()(Aliased const& n)
{
(*this)(n.node);
}

//---------------------------------------------------------------------------//
/*!
* Visit a negated node and append 'not'.
*/
void InfixStringBuilder::operator()(Negated const& n)
{
if (negated_)
{
// Note: this won't happen for simplified expressions but can be for
// testing unsimplified expressions.
*os_ << '!';
}
negated_ = true;
(*this)(n.node);
}

//---------------------------------------------------------------------------//
/*!
* Visit daughter nodes and append the conjunction.
*/
void InfixStringBuilder::operator()(Joined const& n)
{
CELER_EXPECT(n.nodes.size() > 1);

if (negated_)
{
*os_ << '!';
}
negated_ = false;
*os_ << (n.op == op_and ? "all" : n.op == op_or ? "any" : "XXX") << '(';

// Visit first node, then add conjunction for subsequent nodes
auto iter = n.nodes.begin();
(*this)(*iter++);

while (iter != n.nodes.end())
{
*os_ << ", ";
(*this)(*iter++);
}
*os_ << ')';
}

//---------------------------------------------------------------------------//
} // namespace detail
} // namespace orangeinp
} // namespace celeritas
7 changes: 7 additions & 0 deletions test/orange/orangeinp/CsgTreeUtils.test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ TEST_F(CsgTreeUtilsTest, postfix_simplify)
static size_type const expected_lgc[]
= {0u, 1u, logic::lnot, logic::land, 2u, logic::lnot, logic::land};
EXPECT_VEC_EQ(expected_lgc, lgc);
EXPECT_EQ("all(+0, -1, -2)", build_infix_string(tree_, inner_cyl));
}
{
auto lgc = build_postfix(tree_, shell);
Expand All @@ -114,12 +115,15 @@ TEST_F(CsgTreeUtilsTest, postfix_simplify)
logic::lnot,
logic::land};
EXPECT_VEC_EQ(expected_lgc, lgc);
EXPECT_EQ("all(all(+0, -1, -3), !all(+0, -1, -2))",
build_infix_string(tree_, shell));
}
{
auto lgc = build_postfix(tree_, bdy);
static size_type const expected_lgc[]
= {0u, 1u, logic::lnot, logic::land, 4u, logic::land};
EXPECT_VEC_EQ(expected_lgc, lgc);
EXPECT_EQ("all(+0, -1, +4)", build_infix_string(tree_, bdy));
}

// Imply inside boundary
Expand All @@ -135,14 +139,17 @@ TEST_F(CsgTreeUtilsTest, postfix_simplify)
// Simplify once: first simplification is the inner cylinder
min_node = simplify_up(&tree_, min_node);
EXPECT_EQ(NodeId{7}, min_node);
EXPECT_EQ("all(-3, !-2)", build_infix_string(tree_, shell));

// Simplify again: the shell is simplified this time
min_node = simplify_up(&tree_, min_node);
EXPECT_EQ(NodeId{11}, min_node);
EXPECT_EQ("all(+2, -3)", build_infix_string(tree_, shell));

// Simplify one final time: nothing further is simplified
min_node = simplify_up(&tree_, min_node);
EXPECT_EQ(NodeId{}, min_node);
EXPECT_EQ("all(+2, -3)", build_infix_string(tree_, shell));

EXPECT_EQ(
"{0: true, 1: not{0}, 2: ->{0}, 3: ->{1}, 4: ->{0}, 5: surface 2, 6: "
Expand Down
Loading