Skip to content

Commit

Permalink
Add infix string builder (celeritas-project#1121)
Browse files Browse the repository at this point in the history
* Add infix string builder
* Add nodiscard attribute
* Address feedback
  • Loading branch information
sethrj committed Feb 26, 2024
1 parent 8230b45 commit 2a12fc9
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 4 deletions.
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

0 comments on commit 2a12fc9

Please sign in to comment.