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:
-
[Image showing Modulus estimate vs. CFD estimate]
-
[Python script used for model]
# 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