-
-
Notifications
You must be signed in to change notification settings - Fork 129
Water Hammer Tutorial #660
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
base: develop
Are you sure you want to change the base?
Changes from 33 commits
2486383
23f2f8d
47e2198
ee3036d
b602a51
a722414
8348272
3a49db6
02bc955
e59daa4
caba83e
647cc1b
359966d
a9ebbdf
516a4ba
ec8a328
f9b3a5d
d8e9408
8d7204e
2618cbe
65fb3d3
eba2d5e
43e14b7
e79dafa
e01128c
877d12e
bdd79ea
e02da82
2f31599
88cc0c6
18c0427
1d5c577
4bd38d1
d671761
2d679a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,166 @@ | ||||||
| --- | ||||||
| title: Partitioned Water Hammer | ||||||
| keywords: OpenFOAM, Nutils, preCICE, multiscale, fluid, transient | ||||||
| summary: The Partitioned Water Hammer tutorial simulates unsteady pressure wave propagation in pipe systems using different 1D and 3D configurations coupled via preCICE. | ||||||
| --- | ||||||
|
|
||||||
| ## Setup | ||||||
|
|
||||||
| We solve a **partitioned water hammer problem** using a 1D–3D coupling approach. | ||||||
| In this tutorial, the computational domain is split into two coupled regions: a 1D pipe section and a 3D pipe section. | ||||||
| The coupling is performed using **preCICE**. | ||||||
|
|
||||||
| Case setup inspired by [1]. | ||||||
|
||||||
| Case setup inspired by [1]. | |
| The case setup is inspired by [1]. |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| In that study, the cross-section of the pipe was squared. It has been changed to a circular cross-section in the present tutorial. | |
| In that study, the cross-section of the pipe was square. In this tutorial, this has been changed to a circular cross-section. |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would avoid starting a sentence with a code snippet or number:
| `1D` denotes the reduced-order domain (e.g., a Nutils solver) and `3D` denotes the full 3D CFD domain (e.g., OpenFOAM). | |
| In the following, `1D` denotes the reduced-order domain (e.g., a Nutils solver) and `3D` denotes the full 3D CFD domain (e.g., OpenFOAM). |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also use math mode, if you want: https://precice.org/docs-meta-cheatsheet.html#latex-math-syntax
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to duplicate:
| preCICE configuration for the 3D-1D simulation (image generated using the [precice-config-visualizer](https://precice.org/tooling-config-visualization.html)) | |
| preCICE configuration for the 3D-1D simulation: |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * Nutils v9. A Python-based finite element framework. For more information, see the [Nutils adapter documentation](https://precice.org/adapter-nutils.html) | |
| * Nutils. A Python-based finite element framework. For more information, see the [Nutils adapter documentation](https://precice.org/adapter-nutils.html). This Nutils solver requires Nutils v9. |
(for consistency with other tutorials)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default symlink is to 3d1d, but that scenario only comes next. Let's change that to 1d3d.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #!/usr/bin/env sh | ||
| set -e -u | ||
|
|
||
| # shellcheck disable=SC1091 | ||
| . ../tools/cleaning-tools.sh | ||
|
|
||
| clean_tutorial . | ||
| clean_precice_logs . | ||
| rm -fv ./*.log | ||
| rm -fv ./*.vtu | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| import numpy as np | ||
| import matplotlib.pyplot as plt | ||
| import treelog | ||
| from nutils import mesh, function, solver, cli | ||
| import precice | ||
|
|
||
|
|
||
| def main(nelems=200, dt=.005, refdensity=1e3, refpressure=101325.0, psi=1e-6, viscosity=1e-3, theta=0.5): | ||
|
|
||
| # --- preCICE initialization --- | ||
|
|
||
| participant_name = "Fluid1DLeft" | ||
| config_file_name = "../precice-config.xml" | ||
| solver_process_index = 0 | ||
| solver_process_size = 1 | ||
| participant = precice.Participant(participant_name, config_file_name, solver_process_index, solver_process_size) | ||
| mesh_name = "Fluid1DLeft-Mesh" | ||
| velocity_name = "Velocity" | ||
| pressure_name = "Pressure" | ||
| positions = [[0.0, 500.0, 0.0]] # Define a single coupling point (3D format required by preCICE) | ||
| vertex_ids = participant.set_mesh_vertices(mesh_name, positions) | ||
|
|
||
| participant.initialize() | ||
| precice_dt = participant.get_max_time_step_size() | ||
|
|
||
| # --- Nutils domain setup --- | ||
|
|
||
| domain, geom = mesh.rectilinear([np.linspace(0, 500, nelems + 1)]) # 1D domain from 0 to 500 with nelems elements | ||
|
|
||
| ns = function.Namespace() | ||
| ns.x = geom | ||
| ns.dt = dt | ||
| ns.θ = theta | ||
| ns.ρref = refdensity | ||
| ns.pref = refpressure | ||
| ns.pin = 98100 # Inlet pressure (Pa) | ||
| ns.μ = viscosity | ||
| ns.ψ = psi | ||
|
|
||
| # Define basis functions: velocity (vector, degree 2), density (scalar, degree 1) | ||
| ns.ubasis, ns.ρbasis = function.chain([ | ||
| domain.basis('std', degree=2).vector(domain.ndims), | ||
| domain.basis('std', degree=1) | ||
| ]) | ||
|
|
||
| # Define trial and previous-step functions | ||
| ns.u_i = 'ubasis_ni ?lhs_n' | ||
| ns.u0_i = 'ubasis_ni ?lhs0_n' | ||
| ns.ρ = 'ρref + ρbasis_n ?lhs_n' | ||
| ns.ρ0 = 'ρref + ρbasis_n ?lhs0_n' | ||
| ns.p = 'pref + (ρ - ρref) / ψ' | ||
| ns.utheta_i = 'θ u_i + (1 - θ) u0_i' | ||
| ns.ρtheta = 'θ ρ + (1 - θ) ρ0' | ||
|
|
||
| # Cauchy stress tensor: viscous + pressure | ||
| ns.σ_ij = 'μ (utheta_i,j + utheta_j,i) - p δ_ij' | ||
|
|
||
| # --- Weak form residuals --- | ||
|
|
||
| # Mass conservation | ||
| res = domain.integral( | ||
| 'ρbasis_n ((ρ - ρ0) / dt + (ρtheta utheta_k)_,k) d:x' @ ns, | ||
| degree=4 | ||
| ) | ||
|
|
||
| # Momentum conservation: unsteady + convective + diffusive terms | ||
| res += domain.integral( | ||
| '(ubasis_ni (((ρ u_i - ρ0 u0_i) / dt) + (ρtheta utheta_i utheta_j)_,j) + ubasis_ni,j σ_ij) d:x' @ ns, | ||
| degree=4 | ||
| ) | ||
|
|
||
| # Weakly enforced inlet pressure boundary condition | ||
| res += domain.boundary['left'].integral( | ||
| 'pin ubasis_ni n_i d:x' @ ns, | ||
| degree=4 | ||
| ) | ||
|
|
||
| # Initial condition | ||
| lhs0 = np.zeros(res.shape) | ||
|
|
||
| # Time-stepping | ||
| t = 0.0 | ||
| timestep = 0 | ||
| bezier = domain.sample('bezier', 2) | ||
|
|
||
| f = open("watchpoint.txt", "w") | ||
|
|
||
| # --- Time loop with preCICE coupling --- | ||
| while participant.is_coupling_ongoing(): | ||
|
|
||
| # Read velocity data from other solver via preCICE | ||
| u_read = participant.read_data(mesh_name, velocity_name, vertex_ids, precice_dt) | ||
|
|
||
| # Constrain outlet velocity to match coupled value | ||
| stringintegral = f'(u_0 - ({u_read[0][1]}))^2 d:x' | ||
| sqr = domain.boundary['right'].integral(stringintegral @ ns, degree=4) | ||
| cons = solver.optimize('lhs', sqr, droptol=1e-14) | ||
|
|
||
| # Write checkpoint if required by preCICE | ||
| if participant.requires_writing_checkpoint(): | ||
| lhscheckpoint = lhs0 | ||
|
|
||
| # Solve nonlinear system (Newton's method) | ||
| with treelog.context('stokes'): | ||
| lhs = solver.newton( | ||
| 'lhs', | ||
| residual=res, | ||
| constrain=cons, | ||
| arguments=dict(lhs0=lhs0), | ||
| lhs0=lhs0 | ||
| ).solve(1e-08) | ||
|
|
||
| # Evaluate fields for visualization/debugging | ||
| x, p, u, ρ = bezier.eval(['x_i', 'p', 'u_i', 'ρ'] @ ns, arguments=dict(lhs=lhs)) | ||
|
|
||
| # Send pressure at the right boundary to the other solver | ||
| write_press = [[p[-1]]] | ||
| participant.write_data(mesh_name, pressure_name, vertex_ids, write_press) | ||
|
|
||
| # Advance in pseudo-time | ||
| participant.advance(precice_dt) | ||
| precice_dt = participant.get_max_time_step_size() | ||
|
|
||
| # Restore checkpoint if needed | ||
| if participant.requires_reading_checkpoint(): | ||
| lhs0 = lhscheckpoint | ||
| else: | ||
| # Update old solution | ||
| lhs0 = lhs | ||
| timestep += timestep | ||
|
|
||
| # Save probe values (time, inlet pressure, inlet velocity, outlet | ||
| # pressure, outlet velocity, pressure at the middle, velocity at the | ||
| # middle) | ||
| x, p, ρ, u = bezier.eval(['x_i', 'p', 'ρ', 'u_i'] @ ns, lhs=lhs) | ||
| f.write("%e; %e; %e; %e; %e; %e; %e\n" % (t, p[0], u[0], p[-1], u[-1], p[199], u[199])) | ||
| f.flush() | ||
|
|
||
| t += precice_dt # advance pseudo-time | ||
|
|
||
| # Finalize preCICE | ||
| participant.finalize() | ||
| f.close() | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| cli.run(main) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| #!/usr/bin/env sh | ||
| set -e -u | ||
|
|
||
| . ../../tools/cleaning-tools.sh | ||
|
|
||
| rm -f ./results/Fluid1D_* | ||
| clean_precice_logs . | ||
| clean_case_logs . |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| setuptools # required by nutils | ||
| nutils==9 | ||
| numpy >1, <2 | ||
| pyprecice~=3.0 | ||
| matplotlib | ||
| treelog |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| #!/usr/bin/env bash | ||
| set -e -u | ||
|
|
||
| . ../../tools/log.sh | ||
| exec > >(tee --append "$LOGFILE") 2>&1 | ||
|
|
||
| python3 -m venv .venv | ||
| . .venv/bin/activate | ||
| pip install -r requirements.txt && pip freeze > pip-installed-packages.log | ||
|
|
||
| NUTILS_RICHOUTPUT=no python3 Fluid1D.py | ||
|
|
||
| close_log |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since "multiscale" can also refer to micro-macro coupling: