Repository: idaholab/moose
Pull request #30313: Add ParsedSubdomainIDsGenerator
Author: login: miaoyinb
Created: 2025-04-11T21:04:19Z | Merged: 2025-04-14T17:04:14Z

closes #30304


Linked issue #30304: Add ParsedSubdomainIDsGenerator
### Motivation

Per #30282, there is need to assign subdomain ids based on extra element ids. 

### Design

This can be achieved by a new mesh generator (`ParsedSubdomainIDsGenerator`) to assign subdomain ids based on parsed functions dependent on given extra element ids.

### Impact

Better user experience

Diff:
diff --git a/framework/doc/content/source/meshgenerators/ParsedSubdomainIDsGenerator.md b/framework/doc/content/source/meshgenerators/ParsedSubdomainIDsGenerator.md
new file mode 100644
index 000000000000..4539b0d8a238
--- /dev/null
+++ b/framework/doc/content/source/meshgenerators/ParsedSubdomainIDsGenerator.md
@@ -0,0 +1,19 @@
+# ParsedSubdomainIDsGenerator
+
+!syntax description /Mesh/ParsedSubdomainIDsGenerator
+
+## Overview
+
+The `ParsedSubdomainIDsGenerator` generator allows the user to specify parsed expressions to compute the subdomain IDs of elements in the mesh.
+
+The parsed expression is provided by [!param](/Mesh/ParsedSubdomainIDsGenerator/expression), which can a function of the element vertex average coordinates `x`, `y`, and `z`. More importantly, users can use the extra element integer values of each element as the function variables. The names of these extra element integers can be provide through [!param](/Mesh/ParsedSubdomainIDsGenerator/extra_element_id_names). This feature allows users to assign subdomain IDs based on the values of these extra element integers.
+
+When the subdomain IDs are reassigned by this generator, [!param](/Mesh/ParsedSubdomainIDsGenerator/excluded_subdomains) can be used to specify a list of subdomains that need to be retained.
+
+Note that due the flexibility provided by the parsed expression, the function value could be a non-integer value or negative. If a negative value is evaluated for an involved element in the mesh, the mesh generator will throw an error. On the other hand, if a non-integer value is evaluated, the rounded function value is used as the subdomain ID.
+
+!syntax parameters /Mesh/ParsedSubdomainIDsGenerator
+
+!syntax inputs /Mesh/ParsedSubdomainIDsGenerator
+
+!syntax children /Mesh/ParsedSubdomainIDsGenerator
diff --git a/framework/include/meshgenerators/ParsedSubdomainGeneratorBase.h b/framework/include/meshgenerators/ParsedSubdomainGeneratorBase.h
new file mode 100644
index 000000000000..70dbc2292d39
--- /dev/null
+++ b/framework/include/meshgenerators/ParsedSubdomainGeneratorBase.h
@@ -0,0 +1,57 @@
+//* This file is part of the MOOSE framework
+//* https://mooseframework.inl.gov
+//*
+//* All rights reserved, see COPYRIGHT for full restrictions
+//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
+//*
+//* Licensed under LGPL 2.1, please see LICENSE for details
+//* https://www.gnu.org/licenses/lgpl-2.1.html
+
+#pragma once
+
+#include "MeshGenerator.h"
+#include "FunctionParserUtils.h"
+
+/**
+ * A base class for mesh generators that Use a parsed expression to assign new subdomain id(s)
+ */
+class ParsedSubdomainGeneratorBase : public MeshGenerator, public FunctionParserUtils<false>
+{
+public:
+  static InputParameters validParams();
+
+  ParsedSubdomainGeneratorBase(const InputParameters & parameters);
+
+  std::unique_ptr<MeshBase> generate() override;
+
+protected:
+  /// mesh to set the subdomains on
+  std::unique_ptr<MeshBase> & _input;
+
+  /// function expression
+  const std::string _function;
+
+  /// A list of excluded subdomain ids that will not be changed even if they are in the combinatorial geometry
+  std::vector<subdomain_id_type> _excluded_ids;
+
+  /// Names of the extra element ids used in the parsed expression
+  const std::vector<ExtraElementIDName> _eeid_names;
+
+  /// Indices of the extra element ids used in the parsed expression
+  std::vector<unsigned int> _eeid_indices;
+
+  /// function parser object describing the combinatorial geometry
+  SymFunctionPtr _func_F;
+
+  usingFunctionParserUtilsMembers(false);
+
+  /**
+   * Initialize the function parser object
+   * @param function_expression The expression to parse
+   */
+  void functionInitialize(const std::string & function_expression);
+
+  virtual void assignElemSubdomainID(Elem * elem) = 0;
+
+  virtual void setBlockName(std::unique_ptr<MeshBase> &){};
+};
diff --git a/framework/include/meshgenerators/ParsedSubdomainIDsGenerator.h b/framework/include/meshgenerators/ParsedSubdomainIDsGenerator.h
new file mode 100644
index 000000000000..18f14f54f07a
--- /dev/null
+++ b/framework/include/meshgenerators/ParsedSubdomainIDsGenerator.h
@@ -0,0 +1,30 @@
+//* This file is part of the MOOSE framework
+//* https://mooseframework.inl.gov
+//*
+//* All rights reserved, see COPYRIGHT for full restrictions
+//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
+//*
+//* Licensed under LGPL 2.1, please see LICENSE for details
+//* https://www.gnu.org/licenses/lgpl-2.1.html
+
+#pragma once
+
+#include "ParsedSubdomainGeneratorBase.h"
+
+/**
+ * MeshGenerator to use a parsed expression to determine the subdomain ids of included elements.
+ */
+class ParsedSubdomainIDsGenerator : public ParsedSubdomainGeneratorBase
+{
+public:
+  static InputParameters validParams();
+
+  ParsedSubdomainIDsGenerator(const InputParameters & parameters);
+
+protected:
+  /**
+   * Assign the subdomain id to the element based on the parsed expression
+   * @param elem The element to assign the subdomain id to
+   */
+  void assignElemSubdomainID(Elem * elem) override;
+};
diff --git a/framework/include/meshgenerators/ParsedSubdomainMeshGenerator.h b/framework/include/meshgenerators/ParsedSubdomainMeshGenerator.h
index 7a40d8275ac8..cc02be7a43ad 100644
--- a/framework/include/meshgenerators/ParsedSubdomainMeshGenerator.h
+++ b/framework/include/meshgenerators/ParsedSubdomainMeshGenerator.h
@@ -9,42 +9,31 @@
 
 #pragma once
 
-#include "MeshGenerator.h"
-#include "FunctionParserUtils.h"
+#include "ParsedSubdomainGeneratorBase.h"
 
 /**
  * MeshGenerator for defining a Subdomain inside or outside of combinatorial geometry
  */
-class ParsedSubdomainMeshGenerator : public MeshGenerator, public FunctionParserUtils<false>
+class ParsedSubdomainMeshGenerator : public ParsedSubdomainGeneratorBase
 {
 public:
   static InputParameters validParams();
 
   ParsedSubdomainMeshGenerator(const InputParameters & parameters);
 
-  std::unique_ptr<MeshBase> generate() override;
-
 protected:
-  /// mesh to add the subdomain to
-  std::unique_ptr<MeshBase> & _input;
-
-  /// function expression
-  const std::string _function;
-
   /// Block ID to assign to the region
   const subdomain_id_type _block_id;
 
-  /// A list of excluded subdomain ids that will not be changed even if they are in the combinatorial geometry
-  std::vector<subdomain_id_type> _excluded_ids;
-
-  /// Names of the extra element ids used in the parsed expression
-  const std::vector<ExtraElementIDName> _eeid_names;
-
-  /// Indices of the extra element ids used in the parsed expression
-  std::vector<unsigned int> _eeid_indices;
-
-  /// function parser object describing the combinatorial geometry
-  SymFunctionPtr _func_F;
-
-  usingFunctionParserUtilsMembers(false);
+  /**
+   * Assign the subdomain id to the element based on the parsed expression
+   * @param elem The element to assign the subdomain id to
+   */
+  void assignElemSubdomainID(Elem * elem) override;
+
+  /**
+   * Set block name for the block with new id if applicable
+   * @param mesh The mesh to set the block name on
+   */
+  void setBlockName(std::unique_ptr<MeshBase> & mesh) override;
 };
diff --git a/framework/src/meshgenerators/ParsedSubdomainGeneratorBase.C b/framework/src/meshgenerators/ParsedSubdomainGeneratorBase.C
new file mode 100644
index 000000000000..b8c7a9556d56
--- /dev/null
+++ b/framework/src/meshgenerators/ParsedSubdomainGeneratorBase.C
@@ -0,0 +1,126 @@
+//* This file is part of the MOOSE framework
+//* https://mooseframework.inl.gov
+//*
+//* All rights reserved, see COPYRIGHT for full restrictions
+//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
+//*
+//* Licensed under LGPL 2.1, please see LICENSE for details
+//* https://www.gnu.org/licenses/lgpl-2.1.html
+
+#include "ParsedSubdomainGeneratorBase.h"
+#include "Conversion.h"
+#include "MooseMeshUtils.h"
+#include "CastUniquePointer.h"
+
+#include "libmesh/fparser_ad.hh"
+#include "libmesh/elem.h"
+
+InputParameters
+ParsedSubdomainGeneratorBase::validParams()
+{
+  InputParameters params = MeshGenerator::validParams();
+  params += FunctionParserUtils<false>::validParams();
+
+  params.addRequiredParam<MeshGeneratorName>("input", "The mesh we want to modify");
+  params.addRequiredParam<std::string>(
+      "expression", "Parsed expression to determine the subdomain id of each involved element");
+  params.addParam<std::vector<SubdomainName>>(
+      "excluded_subdomains",
+      "A set of subdomain names that will not changed even if "
+      "they are inside/outside the combinatorial geometry");
+  params.addDeprecatedParam<std::vector<subdomain_id_type>>(
+      "excluded_subdomain_ids",
+      "A set of subdomain ids that will not changed even if "
+      "they are inside/outside the combinatorial geometry",
+      "excluded_subdomain_ids is deprecated, use excluded_subdomains (ids or names accepted)");
+  params.addParam<std::vector<std::string>>(
+      "constant_names", {}, "Vector of constants used in the parsed function");
+  params.addParam<std::vector<std::string>>(
+      "constant_expressions",
+      {},
+      "Vector of values for the constants in constant_names (can be an FParser expression)");
+  params.addParam<std::vector<ExtraElementIDName>>(
+      "extra_element_id_names", {}, "Extra element integers used in the parsed expression");
+  params.addClassDescription("A base class for mesh generators that Use a parsed expression to "
+                             "assign new subdomain id(s).");
+
+  return params;
+}
+
+ParsedSubdomainGeneratorBase::ParsedSubdomainGeneratorBase(const InputParameters & parameters)
+  : MeshGenerator(parameters),
+    FunctionParserUtils<false>(parameters),
+    _input(getMesh("input")),
+    _function(parameters.get<std::string>("expression")),
+    _excluded_ids(isParamValid("excluded_subdomain_ids")
+                      ? parameters.get<std::vector<subdomain_id_type>>("excluded_subdomain_ids")
+                      : std::vector<subdomain_id_type>()),
+    _eeid_names(getParam<std::vector<ExtraElementIDName>>("extra_element_id_names"))
+{
+  functionInitialize(_function);
+}
+
+std::unique_ptr<MeshBase>
+ParsedSubdomainGeneratorBase::generate()
+{
+  std::unique_ptr<MeshBase> mesh = std::move(_input);
+
+  // the extra element ids would not have existed at construction so we only do this now
+  for (const auto & eeid_name : _eeid_names)
+    _eeid_indices.push_back(mesh->get_elem_integer_index(eeid_name));
+
+  if (isParamValid("excluded_subdomains"))
+  {
+    auto excluded_subdomains = parameters().get<std::vector<SubdomainName>>("excluded_subdomains");
+
+    // check that the subdomains exist in the mesh
+    for (const auto & name : excluded_subdomains)
+      if (!MooseMeshUtils::hasSubdomainName(*mesh, name))
+        paramError("excluded_subdomains", "The block '", name, "' was not found in the mesh");
+
+    _excluded_ids = MooseMeshUtils::getSubdomainIDs(*mesh, excluded_subdomains);
+  }
+
+  // Loop over the elements
+  for (const auto & elem : mesh->active_element_ptr_range())
+    assignElemSubdomainID(elem);
+
+  // Assign block name, if applicable
+  setBlockName(mesh);
+
+  mesh->set_isnt_prepared();
+  return dynamic_pointer_cast<MeshBase>(mesh);
+}
+
+void
+ParsedSubdomainGeneratorBase::functionInitialize(const std::string & function_expression)
+{
+  // base function object
+  _func_F = std::make_shared<SymFunction>();
+
+  // set FParser internal feature flags
+  setParserFeatureFlags(_func_F);
+
+  // add the constant expressions
+  addFParserConstants(_func_F,
+                      getParam<std::vector<std::string>>("constant_names"),
+                      getParam<std::vector<std::string>>("constant_expressions"));
+
+  // add the extra element integers
+  std::string symbol_str = "x,y,z";
+  for (const auto & eeid_name : _eeid_names)
+    symbol_str += "," + eeid_name;
+
+  // parse function
+  if (_func_F->Parse(function_expression, symbol_str) >= 0)
+    mooseError("Invalid function\n",
+               function_expression,
+               "\nin ",
+               type(),
+               " ",
+               name(),
+               ".\n",
+               _func_F->ErrorMsg());
+
+  _func_params.resize(3 + _eeid_names.size());
+}
diff --git a/framework/src/meshgenerators/ParsedSubdomainIDsGenerator.C b/framework/src/meshgenerators/ParsedSubdomainIDsGenerator.C
new file mode 100644
index 000000000000..6229e1767f4b
--- /dev/null
+++ b/framework/src/meshgenerators/ParsedSubdomainIDsGenerator.C
@@ -0,0 +1,51 @@
+//* This file is part of the MOOSE framework
+//* https://mooseframework.inl.gov
+//*
+//* All rights reserved, see COPYRIGHT for full restrictions
+//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
+//*
+//* Licensed under LGPL 2.1, please see LICENSE for details
+//* https://www.gnu.org/licenses/lgpl-2.1.html
+
+#include "ParsedSubdomainIDsGenerator.h"
+
+registerMooseObject("MooseApp", ParsedSubdomainIDsGenerator);
+
+InputParameters
+ParsedSubdomainIDsGenerator::validParams()
+{
+  InputParameters params = ParsedSubdomainGeneratorBase::validParams();
+
+  params.addClassDescription(
+      "Uses a parsed expression to determine the subdomain ids of included elements.");
+
+  return params;
+}
+
+ParsedSubdomainIDsGenerator::ParsedSubdomainIDsGenerator(const InputParameters & parameters)
+  : ParsedSubdomainGeneratorBase(parameters)
+{
+}
+
+void
+ParsedSubdomainIDsGenerator::assignElemSubdomainID(Elem * elem)
+{
+  _func_params[0] = elem->vertex_average()(0);
+  _func_params[1] = elem->vertex_average()(1);
+  _func_params[2] = elem->vertex_average()(2);
+  for (const auto i : index_range(_eeid_indices))
+    _func_params[3 + i] = elem->get_extra_integer(_eeid_indices[i]);
+
+  if (std::find(_excluded_ids.begin(), _excluded_ids.end(), elem->subdomain_id()) ==
+      _excluded_ids.end())
+  {
+    const Real func_val = evaluate(_func_F);
+    if (func_val < 0.0)
+      paramError("expression",
+                 "the value of the function is negative at the element with ID " +
+                     std::to_string(elem->id()) +
+                     ". The function must be non-negative. Consider using the absolute value of "
+                     "the function.");
+    elem->subdomain_id() = std::round(func_val);
+  }
+}
diff --git a/framework/src/meshgenerators/ParsedSubdomainMeshGenerator.C b/framework/src/meshgenerators/ParsedSubdomainMeshGenerator.C
index a3ecc70c71f3..0db1f6b3cc3e 100644
--- a/framework/src/meshgenerators/ParsedSubdomainMeshGenerator.C
+++ b/framework/src/meshgenerators/ParsedSubdomainMeshGenerator.C
@@ -8,45 +8,21 @@
 //* https://www.gnu.org/licenses/lgpl-2.1.html
 
 #include "ParsedSubdomainMeshGenerator.h"
-#include "Conversion.h"
-#include "MooseMeshUtils.h"
-#include "CastUniquePointer.h"
-
-#include "libmesh/fparser_ad.hh"
-#include "libmesh/elem.h"
 
 registerMooseObject("MooseApp", ParsedSubdomainMeshGenerator);
 
 InputParameters
 ParsedSubdomainMeshGenerator::validParams()
 {
-  InputParameters params = MeshGenerator::validParams();
-  params += FunctionParserUtils<false>::validParams();
+  InputParameters params = ParsedSubdomainGeneratorBase::validParams();
 
-  params.addRequiredParam<MeshGeneratorName>("input", "The mesh we want to modify");
-  params.addRequiredParam<std::string>("combinatorial_geometry",
-                                       "Function expression encoding a combinatorial geometry");
+  params.renameParam("expression",
+                     "combinatorial_geometry",
+                     "Function expression encoding a combinatorial geometry");
   params.addRequiredParam<subdomain_id_type>("block_id",
                                              "Subdomain id to set for inside of the combinatorial");
   params.addParam<SubdomainName>("block_name",
                                  "Subdomain name to set for inside of the combinatorial");
-  params.addParam<std::vector<SubdomainName>>(
-      "excluded_subdomains",
-      "A set of subdomain names that will not changed even if "
-      "they are inside/outside the combinatorial geometry");
-  params.addDeprecatedParam<std::vector<subdomain_id_type>>(
-      "excluded_subdomain_ids",
-      "A set of subdomain ids that will not changed even if "
-      "they are inside/outside the combinatorial geometry",
-      "excluded_subdomain_ids is deprecated, use excluded_subdomains (ids or names accepted)");
-  params.addParam<std::vector<std::string>>(
-      "constant_names", {}, "Vector of constants used in the parsed function");
-  params.addParam<std::vector<std::string>>(
-      "constant_expressions",
-      {},
-      "Vector of values for the constants in constant_names (can be an FParser expression)");
-  params.addParam<std::vector<ExtraElementIDName>>(
-      "extra_element_id_names", {}, "Extra element integers used in the parsed expression");
   params.addClassDescription(
       "Uses a parsed expression (`combinatorial_geometry`) to determine if an "
       "element (via its centroid) is inside the region defined by the expression and "
@@ -56,84 +32,30 @@ ParsedSubdomainMeshGenerator::validParams()
 }
 
 ParsedSubdomainMeshGenerator::ParsedSubdomainMeshGenerator(const InputParameters & parameters)
-  : MeshGenerator(parameters),
-    FunctionParserUtils<false>(parameters),
-    _input(getMesh("input")),
-    _function(parameters.get<std::string>("combinatorial_geometry")),
-    _block_id(parameters.get<SubdomainID>("block_id")),
-    _excluded_ids(isParamValid("excluded_subdomain_ids")
-                      ? parameters.get<std::vector<subdomain_id_type>>("excluded_subdomain_ids")
-                      : std::vector<subdomain_id_type>()),
-    _eeid_names(getParam<std::vector<ExtraElementIDName>>("extra_element_id_names"))
+  : ParsedSubdomainGeneratorBase(parameters),
+    _block_id(parameters.get<subdomain_id_type>("block_id"))
 {
-  // base function object
-  _func_F = std::make_shared<SymFunction>();
-
-  // set FParser internal feature flags
-  setParserFeatureFlags(_func_F);
-
-  // add the constant expressions
-  addFParserConstants(_func_F,
-                      getParam<std::vector<std::string>>("constant_names"),
-                      getParam<std::vector<std::string>>("constant_expressions"));
-
-  // add the extra element integers
-  std::string symbol_str = "x,y,z";
-  for (const auto & eeid_name : _eeid_names)
-    symbol_str += "," + eeid_name;
-
-  // parse function
-  if (_func_F->Parse(_function, symbol_str) >= 0)
-    mooseError("Invalid function\n",
-               _function,
-               "\nin ParsedSubdomainMeshGenerator ",
-               name(),
-               ".\n",
-               _func_F->ErrorMsg());
-
-  _func_params.resize(3 + _eeid_names.size());
 }
 
-std::unique_ptr<MeshBase>
-ParsedSubdomainMeshGenerator::generate()
+void
+ParsedSubdomainMeshGenerator::assignElemSubdomainID(Elem * elem)
 {
-  std::unique_ptr<MeshBase> mesh = std::move(_input);
-
-  // the extra element ids would not have existed at construction so we only do this now
-  for (const auto & eeid_name : _eeid_names)
-    _eeid_indices.push_back(mesh->get_elem_integer_index(eeid_name));
-
-  if (isParamValid("excluded_subdomains"))
-  {
-    auto excluded_subdomains = parameters().get<std::vector<SubdomainName>>("excluded_subdomains");
-
-    // check that the subdomains exist in the mesh
-    for (const auto & name : excluded_subdomains)
-      if (!MooseMeshUtils::hasSubdomainName(*mesh, name))
-        paramError("excluded_subdomains", "The block '", name, "' was not found in the mesh");
-
-    _excluded_ids = MooseMeshUtils::getSubdomainIDs(*mesh, excluded_subdomains);
-  }
-
-  // Loop over the elements
-  for (const auto & elem : mesh->active_element_ptr_range())
-  {
-    _func_params[0] = elem->vertex_average()(0);
-    _func_params[1] = elem->vertex_average()(1);
-    _func_params[2] = elem->vertex_average()(2);
-    for (const auto i : index_range(_eeid_indices))
-      _func_params[3 + i] = elem->get_extra_integer(_eeid_indices[i]);
-    bool contains = evaluate(_func_F);
-
-    if (contains && std::find(_excluded_ids.begin(), _excluded_ids.end(), elem->subdomain_id()) ==
-                        _excluded_ids.end())
-      elem->subdomain_id() = _block_id;
-  }
+  _func_params[0] = elem->vertex_average()(0);
+  _func_params[1] = elem->vertex_average()(1);
+  _func_params[2] = elem->vertex_average()(2);
+  for (const auto i : index_range(_eeid_indices))
+    _func_params[3 + i] = elem->get_extra_integer(_eeid_indices[i]);
+  bool contains = evaluate(_func_F);
+
+  if (contains && std::find(_excluded_ids.begin(), _excluded_ids.end(), elem->subdomain_id()) ==
+                      _excluded_ids.end())
+    elem->subdomain_id() = _block_id;
+}
 
-  // Assign block name, if provided
+void
+ParsedSubdomainMeshGenerator::setBlockName(std::unique_ptr<MeshBase> & mesh)
+{
   if (isParamValid("block_name"))
-    mesh->subdomain_name(_block_id) = getParam<SubdomainName>("block_name");
-
-  mesh->set_isnt_prepared();
-  return dynamic_pointer_cast<MeshBase>(mesh);
+    mesh->subdomain_name(getParam<subdomain_id_type>("block_id")) =
+        getParam<SubdomainName>("block_name");
 }
diff --git a/test/tests/meshgenerators/parsed_subdomain_ids_generator/gold/parsed_eeid_in.e b/test/tests/meshgenerators/parsed_subdomain_ids_generator/gold/parsed_eeid_in.e
new file mode 100644
index 000000000000..6b332a2238d2
Binary files /dev/null and b/test/tests/meshgenerators/parsed_subdomain_ids_generator/gold/parsed_eeid_in.e differ
diff --git a/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_eeid.i b/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_eeid.i
new file mode 100644
index 000000000000..aa43115040a0
--- /dev/null
+++ b/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_eeid.i
@@ -0,0 +1,24 @@
+[Mesh]
+  [gmg]
+    type = GeneratedMeshGenerator
+    dim = 2
+    xmax = 1
+    xmin = -1
+    ymax = 1
+    ymin = -1
+    nx = 2
+    ny = 2
+  []
+  [add_eeid]
+    type = ParsedExtraElementIDGenerator
+    input = gmg
+    expression = 'if(x>0,if(y>0,1,4),if(y>0,2,3))'
+    extra_elem_integer_name = 'eeid'
+  []
+  [psidg]
+    type = ParsedSubdomainIDsGenerator
+    input = add_eeid
+    expression = 'eeid'
+    extra_element_id_names = 'eeid'
+  []
+[]
diff --git a/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_xy.i b/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_xy.i
new file mode 100644
index 000000000000..b456abfa92ae
--- /dev/null
+++ b/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_xy.i
@@ -0,0 +1,23 @@
+[Mesh]
+  [gmg]
+    type = GeneratedMeshGenerator
+    dim = 2
+    xmax = 1
+    xmin = -1
+    ymax = 1
+    ymin = -1
+    nx = 2
+    ny = 2
+  []
+  [add_eeid]
+    type = ParsedExtraElementIDGenerator
+    input = gmg
+    expression = 'if(x>0,if(y>0,1,4),if(y>0,2,3))'
+    extra_elem_integer_name = 'eeid'
+  []
+  [psidg]
+    type = ParsedSubdomainIDsGenerator
+    input = add_eeid
+    expression = 'if(x>0,if(y>0,1,4),if(y>0,2,3))'
+  []
+[]
diff --git a/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_xy_negative.i b/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_xy_negative.i
new file mode 100644
index 000000000000..99de0ec78016
--- /dev/null
+++ b/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_xy_negative.i
@@ -0,0 +1,23 @@
+[Mesh]
+  [gmg]
+    type = GeneratedMeshGenerator
+    dim = 2
+    xmax = 1
+    xmin = -1
+    ymax = 1
+    ymin = -1
+    nx = 2
+    ny = 2
+  []
+  [add_eeid]
+    type = ParsedExtraElementIDGenerator
+    input = gmg
+    expression = 'if(x>0,if(y>0,1,4),if(y>0,2,3))'
+    extra_elem_integer_name = 'eeid'
+  []
+  [psidg]
+    type = ParsedSubdomainIDsGenerator
+    input = add_eeid
+    expression = 'if(x>0,if(y>0,1,-4),if(y>0,2,3))'
+  []
+[]
diff --git a/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_xy_rounded.i b/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_xy_rounded.i
new file mode 100644
index 000000000000..177716b15c59
--- /dev/null
+++ b/test/tests/meshgenerators/parsed_subdomain_ids_generator/parsed_xy_rounded.i
@@ -0,0 +1,23 @@
+[Mesh]
+  [gmg]
+    type = GeneratedMeshGenerator
+    dim = 2
+    xmax = 1
+    xmin = -1
+    ymax = 1
+    ymin = -1
+    nx = 2
+    ny = 2
+  []
+  [add_eeid]
+    type = ParsedExtraElementIDGenerator
+    input = gmg
+    expression = 'if(x>0,if(y>0,1,4),if(y>0,2,3))'
+    extra_elem_integer_name = 'eeid'
+  []
+  [psidg]
+    type = ParsedSubdomainIDsGenerator
+    input = add_eeid
+    expression = 'if(x>0,if(y>0,1.2,3.8),if(y>0,2.3,2.9))'
+  []
+[]
diff --git a/test/tests/meshgenerators/parsed_subdomain_ids_generator/tests b/test/tests/meshgenerators/parsed_subdomain_ids_generator/tests
new file mode 100644
index 000000000000..c56831d95d46
--- /dev/null
+++ b/test/tests/meshgenerators/parsed_subdomain_ids_generator/tests
@@ -0,0 +1,35 @@
+[Tests]
+  design = 'meshgenerators/ParsedSubdomainIDsGenerator.md'
+  issues = '#30304'
+  [parsed_eeid]
+    type = 'Exodiff'
+    input = 'parsed_eeid.i'
+    cli_args = '--mesh-only'
+    exodiff = 'parsed_eeid_in.e'
+    recover = false
+    requirement = 'The system shall have the ability to assign subdomain ids based on a parsed function of an extra element id.'
+  []
+  [parsed_xy]
+    type = 'Exodiff'
+    input = 'parsed_xy.i'
+    cli_args = '--mesh-only parsed_eeid_in.e'
+    exodiff = 'parsed_eeid_in.e'
+    recover = false
+    requirement = 'The system shall have the ability to assign subdomain ids based on a parsed function of element vertex average coordination.'
+  []
+  [parsed_xy_rounded]
+    type = 'Exodiff'
+    input = 'parsed_xy_rounded.i'
+    cli_args = '--mesh-only parsed_eeid_in.e'
+    exodiff = 'parsed_eeid_in.e'
+    recover = false
+    requirement = 'The system shall have the ability to assign subdomain ids based on a parsed function of element vertex average coordination after rounding up and taking absolute value.'
+  []
+  [parsed_xy_negative]
+    type = RunException
+    input = 'parsed_xy_negative.i'
+    cli_args = '--mesh-only err.e'
+    expect_err = 'the value of the function is negative'
+    requirement = 'The system shall throw an error if the parsed function gives a negative value for an element.'
+  []
+[]
diff --git a/test/tests/meshgenerators/parsed_subdomain_mesh_generator/tests b/test/tests/meshgenerators/parsed_subdomain_mesh_generator/tests
index 79db4782aafd..f837e83def04 100644
--- a/test/tests/meshgenerators/parsed_subdomain_mesh_generator/tests
+++ b/test/tests/meshgenerators/parsed_subdomain_mesh_generator/tests
@@ -5,7 +5,6 @@
     input = 'parsed_subdomain_mg.i'
     cli_args = '--mesh-only'
     exodiff = 'parsed_subdomain_mg_in.e'
-    mesh_mode = 'REPLICATED'
     recover = false
     requirement = 'The system shall have the ability to assign mesh subdomains based on parsed spatial functions.'
     issues = '#11640'
@@ -15,7 +14,6 @@
     input = 'parsed_subdomain_eeid.i'
     cli_args = '--mesh-only'
     exodiff = 'parsed_subdomain_eeid_in.e'
-    mesh_mode = 'REPLICATED'
     recover = false
     requirement = 'The system shall have the ability to assign mesh subdomains based on parsed functions of extra element integers.'
     issues = '#26402'


Reviews:
user: login: GiudGiud
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T08:54:14Z
user: login: GiudGiud
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T08:13:50Z

Review comments:
diff_hunk: @@ -0,0 +1,55 @@
+//* This file is part of the MOOSE framework
+//* https://mooseframework.inl.gov
+//*
+//* All rights reserved, see COPYRIGHT for full restrictions
+//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
+//*
+//* Licensed under LGPL 2.1, please see LICENSE for details
+//* https://www.gnu.org/licenses/lgpl-2.1.html
+
+#pragma once
+
+#include "MeshGenerator.h"
+#include "FunctionParserUtils.h"
+
+/**
+ * A base class for mesh generators that Use a parsed expression to assign new subdomain id(s)
+ */
+class ParsedSubdomainGeneratorBase : public MeshGenerator, public FunctionParserUtils<false>
+{
+public:
+  static InputParameters validParams();
+
+  ParsedSubdomainGeneratorBase(const InputParameters & parameters);
+
+  std::unique_ptr<MeshBase> generate() override;
+
+protected:
+  /// mesh to add the subdomain to
path: framework/include/meshgenerators/ParsedSubdomainGeneratorBase.h
user: login: GiudGiud
body: ```suggestion
  /// mesh to set the subdomains on
```

created_at: 2025-04-12T08:44:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,55 @@
+//* This file is part of the MOOSE framework
+//* https://mooseframework.inl.gov
+//*
+//* All rights reserved, see COPYRIGHT for full restrictions
+//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
+//*
+//* Licensed under LGPL 2.1, please see LICENSE for details
+//* https://www.gnu.org/licenses/lgpl-2.1.html
+
+#pragma once
+
+#include "MeshGenerator.h"
+#include "FunctionParserUtils.h"
+
+/**
+ * A base class for mesh generators that Use a parsed expression to assign new subdomain id(s)
+ */
+class ParsedSubdomainGeneratorBase : public MeshGenerator, public FunctionParserUtils<false>
+{
+public:
+  static InputParameters validParams();
+
+  ParsedSubdomainGeneratorBase(const InputParameters & parameters);
+
+  std::unique_ptr<MeshBase> generate() override;
+
+protected:
+  /// mesh to add the subdomain to
+  std::unique_ptr<MeshBase> & _input;
+
+  /// Block ID to assign to the region
path: framework/include/meshgenerators/ParsedSubdomainGeneratorBase.h
user: login: GiudGiud
body: Probably should only be in the classic ParsedSubdomainGenerator?
created_at: 2025-04-12T08:44:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8,45 +8,20 @@
 //* https://www.gnu.org/licenses/lgpl-2.1.html
 
 #include "ParsedSubdomainMeshGenerator.h"
-#include "Conversion.h"
-#include "MooseMeshUtils.h"
-#include "CastUniquePointer.h"
-
-#include "libmesh/fparser_ad.hh"
-#include "libmesh/elem.h"
 
 registerMooseObject("MooseApp", ParsedSubdomainMeshGenerator);
 
 InputParameters
 ParsedSubdomainMeshGenerator::validParams()
 {
-  InputParameters params = MeshGenerator::validParams();
-  params += FunctionParserUtils<false>::validParams();
+  InputParameters params = ParsedSubdomainGeneratorBase::validParams();
 
-  params.addRequiredParam<MeshGeneratorName>("input", "The mesh we want to modify");
   params.addRequiredParam<std::string>("combinatorial_geometry",
path: framework/src/meshgenerators/ParsedSubdomainMeshGenerator.C
user: login: GiudGiud
body: I would move this to the base class with a generic "expression" name then just rename it in the derived class as needed

That way the logic for the expression string can move to the base class
created_at: 2025-04-12T08:50:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,19 @@
+# ParsedSubdomainIDsGenerator
+
+!syntax description /Mesh/ParsedSubdomainIDsGenerator
+
+## Overview
+
+The `ParsedSubdomainIDsGenerator` generator allows the user to specify parsed expressions to compute the subdomain IDs of elements in the mesh.
+
+The parsed expression is provided by [!param](/Mesh/ParsedSubdomainIDsGenerator/expression), which can a function of the element centroid coordinates `x`, `y`, and `z`. More importantly, users can use the extra element integer values of each element as the function variables. The names of these extra element integers can be provide through [!param](/Mesh/ParsedSubdomainIDsGenerator/extra_element_id_names). This feature allows users to assign subdomain IDs based on the values of these extra element integers.
+
+When the subdomain IDs are reassigned by this generator, [!param](/Mesh/ParsedSubdomainIDsGenerator/excluded_subdomains) can be used to specify a list of subdomains that need to be retained.
+
+Note that due the flexibility provided by the parsed expression, the function value could be a non-integer value or negative. In that case, the absolute value of the rounded function value is used as the subdomain ID.
path: framework/doc/content/source/meshgenerators/ParsedSubdomainIDsGenerator.md
user: login: GiudGiud
body: I think I would rather an error for a negative value? (rounding is fine) What is your use case for a negative value ?
created_at: 2025-04-12T08:54:08Z
author_association: CONTRIBUTOR

PR comments:
user: login: moosebuild
created_at: 2025-04-11T23:57:58Z
body: Job [Documentation](https://civet.inl.gov/job/2814822/), step Docs: sync website on 9357aee wanted to post the following:

View the site [here](https://mooseframework.inl.gov/docs/PRs/30313/site)

This comment will be updated on new commits.
author_association: CONTRIBUTOR
user: login: moosebuild
created_at: 2025-04-12T00:45:16Z
body: Job [Coverage](https://civet.inl.gov/job/2814868/), step Generate coverage on 9357aee wanted to post the following:


## Framework coverage

<table>
  <thead>
    <tr>
      <th></th>
      <th>e50d7a</th>
      <th colspan=4>#30313 9357ae</th>
    </tr>
    <tr>
      <th></th>
      <th>Total</th>
      <th>Total</th>
      <th>+/-</th>
      <th>New</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><b>Rate</b></td>
      <td>85.30%</td>
      <td>85.30%</td>
      <td>+0.00%</td>
      <td>93.55%</td>
    </tr>
    <tr>
      <td><b>Hits</b></td>
      <td>109959</td>
      <td>109998</td>
      <td>+39</td>
      <td>87</td>
    </tr>
    <tr>
      <td><b>Misses</b></td>
      <td>18955</td>
      <td>18957</td>
      <td>+2</td>
      <td>6</td>
    </tr>
  </tbody>
</table>

[Diff coverage report](https://mooseframework.inl.gov/docs/PRs/30313/coverage_diff/framework)

[Full coverage report](https://mooseframework.inl.gov/docs/PRs/30313/coverage/framework)


## Modules coverage

Coverage did not change

## Full coverage reports

<details>
<summary>Reports</summary>

- [`framework`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/framework)
- [`chemical_reactions`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/chemical_reactions)
- [`combined`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/combined)
- [`contact`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/contact)
- [`electromagnetics`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/electromagnetics)
- [`external_petsc_solver`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/external_petsc_solver)
- [`fluid_properties`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/fluid_properties)
- [`fsi`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/fsi)
- [`functional_expansion_tools`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/functional_expansion_tools)
- [`geochemistry`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/geochemistry)
- [`heat_transfer`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/heat_transfer)
- [`level_set`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/level_set)
- [`misc`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/misc)
- [`navier_stokes`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/navier_stokes)
- [`optimization`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/optimization)
- [`peridynamics`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/peridynamics)
- [`phase_field`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/phase_field)
- [`porous_flow`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/porous_flow)
- [`ray_tracing`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/ray_tracing)
- [`rdg`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/rdg)
- [`reactor`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/reactor)
- [`richards`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/richards)
- [`scalar_transport`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/scalar_transport)
- [`solid_mechanics`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/solid_mechanics)
- [`solid_properties`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/solid_properties)
- [`stochastic_tools`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/stochastic_tools)
- [`subchannel`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/subchannel)
- [`thermal_hydraulics`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/thermal_hydraulics)
- [`xfem`](https://mooseframework.inl.gov/docs/PRs/30313/coverage/xfem)

</details>



This comment will be updated on new commits.
author_association: CONTRIBUTOR
user: login: moosebuild
created_at: 2025-04-13T04:13:18Z
body: Job [Precheck](https://civet.inl.gov/job/2814777/), step Clang format on 25064c7 wanted to post the following:

Your code requires style changes.  

A patch was auto generated and copied [here](https://mooseframework.inl.gov/docs/PRs/30313/clang_format/style.patch)  
You can directly apply the patch by running, in the top level of your repository:

`curl -s https://mooseframework.inl.gov/docs/PRs/30313/clang_format/style.patch | git apply -v`  

Alternatively, with your repository up to date and in the top level of your repository:

`git clang-format e50d7a5eb0ef86f202bfed09c19b167b63460ed0`

author_association: CONTRIBUTOR
user: login: GiudGiud
created_at: 2025-04-14T17:04:09Z
body: Distrivuted failures unrelated and fixed already

Debug failures unrelated (neml2) and also fixed
author_association: CONTRIBUTOR