modified: anasen_fem/clean.sh new file: anasen_fem/dotproduct.py new file: anasen_fem/paraview_dotproduct.py modified: anasen_fem/paraview_plotter.py modified: anasen_fem/run.py modified: anasen_fem/scalars.dat.names new file: anasen_fem/scalars_weight.dat new file: anasen_fem/scalars_weight.dat.names modified: anasen_fem/wires2d.sif new file: anasen_fem/wires2d_weight.sif modified: anasen_fem/wires_gmsh2d_bc.py
114 lines
5.2 KiB
Python
Executable File
114 lines
5.2 KiB
Python
Executable File
import os
|
|
import sys
|
|
|
|
# Cathode wire (0..23) that wires2d_weight.sif drives to 1 V. Passed to the
|
|
# mesher, which puts that one wire in its own physical group, tag 40.
|
|
SELECTED_CATHODE = 1
|
|
|
|
# 0 = everything in one pass
|
|
# 1 = mesh and the physical field only
|
|
# 2 = weighting field and dot product only, reusing stage 1's mesh
|
|
STAGE = 0
|
|
|
|
# gmsh comes from apt and lives in the system python3; meshio is pip-installed
|
|
# into the venv, which cannot see apt packages. Neither interpreter has both,
|
|
# so name them explicitly rather than relying on whichever python3 is on PATH.
|
|
MESH_PY = "/usr/bin/python3"
|
|
DOT_PY = os.path.expanduser("~/myenv/bin/python3")
|
|
|
|
|
|
def run(cmd):
|
|
"""os.system that stops on failure.
|
|
|
|
Worth the four lines: a solve that quietly did nothing once produced a
|
|
field of zeros, and every later step ran happily on it.
|
|
"""
|
|
print(cmd)
|
|
if os.system(cmd) != 0:
|
|
sys.exit("failed: " + cmd)
|
|
|
|
|
|
# z-loci to run, in mm.
|
|
#
|
|
# The wires twist: over z = 0 -> 174.3 the anode lattice turns 60 deg against
|
|
# the cathode lattice, which is exactly 4 cathode pitches of 15 deg. So the
|
|
# field pattern goes through the same cycle four times across that range, while
|
|
# the wire radii drift slowly and monotonically (anodes 32.0 -> 37.0 mm).
|
|
#
|
|
# A uniform 17.43 step samples the fast cycle 2.75 times per period, which
|
|
# aliases it -- consecutive slices land at relative azimuths 7.5, 14.1, 5.7,
|
|
# 12.2, 3.5 deg, jumping around rather than sweeping. Sample the two variations
|
|
# separately instead:
|
|
# - 8 slices across one pitch, which walks the azimuth in even ~2 deg steps
|
|
# at essentially fixed radius
|
|
# - 5 slices one whole pitch apart, which holds the azimuth near-fixed and
|
|
# walks the radius over its full range
|
|
# 12 z-loci in total, against 11 for the uniform sweep.
|
|
PITCH_Z = 174.3 / 4.0
|
|
|
|
Z_VALUES = sorted(set(
|
|
[round(i * PITCH_Z / 8.0, 4) for i in range(8)]
|
|
+ [round(i * PITCH_Z, 4) for i in range(5)]
|
|
))
|
|
|
|
# Running only two loci for now; delete this line for the full sweep.
|
|
# 19.794 is the crossover: the relative azimuth wraps through zero there, so an
|
|
# anode sits directly radially outside a cathode, and the closest anode-cathode
|
|
# approach over the whole range (4.339 mm) is at 19.707. It is deliberately off
|
|
# the grid above, whose nearest point is 21.7875.
|
|
Z_VALUES = [0.0, 19.794]
|
|
|
|
# Stage 2 reuses whatever single mesh is sitting in wires2d/, so it cannot span
|
|
# several z. Without this it would happily solve the weighting field on one z's
|
|
# mesh and archive the result under another z's name -- and dotproduct.py could
|
|
# not catch it, since the node sets would agree perfectly.
|
|
if STAGE == 2 and len(Z_VALUES) > 1:
|
|
sys.exit(
|
|
"STAGE 2 applies to the one mesh in wires2d/, which is whichever z\n"
|
|
"stage 1 ran last. Set Z_VALUES to that single z."
|
|
)
|
|
|
|
for count, val in enumerate(Z_VALUES, start=10):
|
|
print(val)
|
|
os.system("mkdir -p wires2d/mesh_files wires2d/sif_files wires2d/vtu_files png")
|
|
|
|
# Each stage archives its own output before the next one starts, so a
|
|
# failure in stage 2 does not throw away what stage 1 already produced.
|
|
if STAGE in (0, 1):
|
|
run("%s wires_gmsh2d_bc.py %s %d" % (MESH_PY, val, SELECTED_CATHODE))
|
|
run("ElmerGrid 14 2 wires2d.msh -2d")
|
|
run("ElmerSolver wires2d.sif")
|
|
run("./paraview_plotter.py")
|
|
|
|
os.system("cp wires2d.msh wires2d/mesh_files/wires2d%02d_%1.4f.msh"%(count,val))
|
|
os.system("cp wires2d.sif wires2d/sif_files/wires2d_%02d_%1.4f.sif"%(count,val))
|
|
os.system("cp wires2d/elfield_anasen_t0001.vtu wires2d/vtu_files/elfield_anasen_%02d_%1.4f.vtu"%(count,val))
|
|
os.system("cp contour_output.png png/Contour_output_z_%02d_%1.4f.png"%(count,val))
|
|
os.system("cp contour_quarter_output.png png/Contour_output_z_%02d_%1.4f_quarter.png"%(count,val))
|
|
os.system("cp Field_output.png png/Field_ouput_z_%02d_%1.4f.png"%(count,val))
|
|
os.system("cp Streamlines_quarter_output.png png/Streamlines_output_z_%02d_%1.4f_quarter.png"%(count,val))
|
|
|
|
# Stage 2 does not re-mesh: it reuses wires2d/ as stage 1 left it, which is
|
|
# what makes the node-by-node dot product valid.
|
|
if STAGE in (0, 2):
|
|
run("ElmerSolver wires2d_weight.sif")
|
|
run("%s dotproduct.py" % DOT_PY)
|
|
run("./paraview_dotproduct.py")
|
|
|
|
os.system("cp wires2d_weight.sif wires2d/sif_files/wires2d_weight_%02d_%1.4f.sif"%(count,val))
|
|
os.system("cp wires2d/elfield_weight_t0001.vtu wires2d/vtu_files/elfield_weight_%02d_%1.4f.vtu"%(count,val))
|
|
os.system("cp wires2d/dotproduct.vtu wires2d/vtu_files/dotproduct_%02d_%1.4f.vtu"%(count,val))
|
|
os.system("cp DotProduct_output.png png/DotProduct_output_z_%02d_%1.4f.png"%(count,val))
|
|
os.system("cp DotProduct_quarter_output.png png/DotProduct_output_z_%02d_%1.4f_quarter.png"%(count,val))
|
|
|
|
# os.system("python3 garfield_sim.py")
|
|
|
|
# (a bare "break" here runs only the first z, as the original did)
|
|
|
|
# os.system("tar -cvzf wiress2d/mesh.tar.gz wires2d/mesh_files")
|
|
# os.system("rm -rf wires2d/mesh_files/*")
|
|
# os.system("tar -cvzf wires2d/sif.tar.gz wires2d/sif_files")
|
|
# os.system("rm -rf wires2d/sif_files/*")
|
|
# os.system("tar -cvzf wires2d/vtu.tar.gz wires2d/vtu_files")
|
|
# os.system("rm -rf wires2d/vtu_files/*")
|