Issues with Flow Propagation in Room Simulation: Model Struggles to Predict Flow Beyond the Supply Zone

Hello,

I am working on predicting airflow inside a room, where the supply is positioned on the ceiling (image attached). However, I am encountering an issue where the model predicts the flow correctly near the supply, but the flow does not propagate effectively to the rest of the room. This results in a significant deviation between the model’s predictions and the CFD estimates, particularly in terms of velocity magnitude distribution.

I have attached an image comparing the Modulus-predicted velocity magnitude field with the CFD simulation results. As you can see, the predictions are largely deviated, especially farther from the supply.

I am using the script and configuration file below to run the simulation, but I believe there may be an issue with either the model setup or constraints that is preventing proper flow propagation. Could someone help me understand what might be causing this issue and how I can resolve it?

Attached:

# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES.
# SPDX-FileCopyrightText: All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import warnings

import torch
import numpy as np
from sympy import Symbol, sqrt, Max

import modulus.sym
from modulus.sym.hydra import to_absolute_path, instantiate_arch, ModulusConfig
from modulus.sym.solver import Solver
from modulus.sym.domain import Domain
from modulus.sym.domain.constraint import (
    PointwiseBoundaryConstraint,
    PointwiseInteriorConstraint,
    IntegralBoundaryConstraint,
)
from modulus.sym.domain.validator import PointwiseValidator
from modulus.sym.domain.monitor import PointwiseMonitor
from modulus.sym.key import Key
from modulus.sym.eq.pdes.navier_stokes import NavierStokes
from modulus.sym.eq.pdes.basic import NormalDotVec
from modulus.sym.utils.io import csv_to_dict
from modulus.sym.geometry.tessellation import Tessellation


@modulus.sym.main(config_path="conf", config_name="config")
def run(cfg: ModulusConfig) -> None:
    # read stl files to make geometry
    point_path = to_absolute_path("./stl_files")
    inlet_mesh = Tessellation.from_stl(
        point_path + "/Supply.stl", airtight=False
    )
    outlet_mesh = Tessellation.from_stl(
        point_path + "/Returns.stl", airtight=False
    )
    wall_mesh = Tessellation.from_stl(
        point_path + "/Room.stl", airtight=False
    )
    glass_mesh = Tessellation.from_stl(
        point_path + "/Glass.stl", airtight=False
    )
    interior_mesh = Tessellation.from_stl(
        point_path + "/RoomClosed.stl", airtight=True
    )

    # params
    nu = 15e-06
    inlet_vel = -0.3628571434

    # Define constant velocity values
    def constant_velocity(x, y, z, velocity):
        u = velocity[0]
        v = velocity[1]
        w = velocity[2]
        return u, v, w

    # make aneurysm domain
    domain = Domain()

    # make list of nodes to unroll graph on
    ns = NavierStokes(nu=nu, rho=1.0, dim=3, time=False)
    normal_dot_vel = NormalDotVec(["u", "v", "w"])

    flow_net = instantiate_arch(
        input_keys=[Key("x"), Key("y"), Key("z")],
        output_keys=[Key("u"), Key("v"), Key("w"), Key("p")],
        cfg=cfg.arch.fully_connected,
    )
    nodes = (
        ns.make_nodes()
        + normal_dot_vel.make_nodes()
        + [flow_net.make_node(name="flow_network")]
    )

    # Inlet velocity (constant)
    inlet_velocity = (0, 0, inlet_vel)  # Set constant velocity as a tuple (u, v, w)

    # Add constraints to solver
    # Inlet
    u, v, w = constant_velocity(
        Symbol("x"),
        Symbol("y"),
        Symbol("z"),
        velocity=inlet_velocity
    )

    inlet = PointwiseBoundaryConstraint(
        nodes=nodes,
        geometry=inlet_mesh,
        outvar={"u": u, "v": v, "w": w},
        batch_size=cfg.batch_size.inlet,
    )
    domain.add_constraint(inlet, "inlet")

    # outlet
    outlet = PointwiseBoundaryConstraint(
        nodes=nodes,
        geometry=outlet_mesh,
        outvar={"p": 0},
        batch_size=cfg.batch_size.outlet,
    )
    domain.add_constraint(outlet, "outlet")

    # no slip
    no_slip = PointwiseBoundaryConstraint(
        nodes=nodes,
        geometry=wall_mesh,
        outvar={"u": 0, "v": 0, "w": 0},
        batch_size=cfg.batch_size.no_slip,
    )
    
    domain.add_constraint(no_slip, "no_slip")
    # no slip
    glass_Noslip = PointwiseBoundaryConstraint(
        nodes=nodes,
        geometry=glass_mesh,
        outvar={"u": 0, "v": 0, "w": 0},
        batch_size=cfg.batch_size.no_slip,
    )

    domain.add_constraint(glass_Noslip, "glass_Noslip")
    # interior
    interior = PointwiseInteriorConstraint(
        nodes=nodes,
        geometry=interior_mesh,
        outvar={"continuity": 0, "momentum_x": 0, "momentum_y": 0, "momentum_z": 0},
        batch_size=cfg.batch_size.interior,
    )
    domain.add_constraint(interior, "interior")


    # add validation data
    
    # add pressure monitor
    pressure_monitor = PointwiseMonitor(
        inlet_mesh.sample_boundary(16),
        output_names=["p"],
        metrics={"pressure_drop": lambda var: torch.mean(var["p"])},
        nodes=nodes,
    )
    domain.add_monitor(pressure_monitor)

    # make solver
    slv = Solver(cfg, domain)

    # start solver
    slv.solve()

if __name__ == "__main__":
    run()
  • [YAML configuration file]

defaults :
  - modulus_default
  - arch:
      - fully_connected
  - scheduler: tf_exponential_lr
  - optimizer: adam
  - loss: sum
  - _self_

arch:
    fully_connected:
        layer_size: 512
        nr_layers: 6
        
scheduler:
  decay_rate: 0.95
  decay_steps: 15000

training:
  rec_results_freq : 2000
  rec_constraint_freq: 2000
  max_steps : 350000

batch_size:
  inlet: 650
  outlet: 300
  no_slip: 3200
  interior: 5000
  glass_Noslip: 310

1 Like

Hi,
You can try adding integral planes constraints to force the network to send mass through your geometry. But modelling turbulent flows in 3D with PINNs is extremely difficult. It is an active area of research I believe and has not yet been solved.
Best regards,
Andreas.

1 Like

Hi Ahal,

Thank you for your input!

Currently, I am not modeling turbulence in either the Modulus model or the OpenFOAM case, which might have some impact on the results. I noticed that the predicted pressure field seems incorrect. Specifically:

  • The outlet boundary condition is set to zero pressure (p = 0).
  • However, I am observing negative pressure values at the supply, which seems counterintuitive.

I’ve attached a pressure contour plot for reference f. As you can see, there are regions with significant negative pressure at the supply location. The physical setup involves:

  • A supply (blue zone) is located on the ceiling.
  • A return at the left lower wall, does not any pressure differential created as illustrated in the second diagram.

Do you have any thoughts on why this discrepancy might occur? Could it be due to:

  1. Numerical issues related to the boundary conditions?
  2. The absence of turbulence modeling causing inaccuracies in pressure prediction?

Any insights would be greatly appreciated! I’d love to hear your suggestions for debugging or improving the setup.

2 Likes