Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions csauto/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def build_run_command(
case_dir: Path,
nprocs: int,
nt: int,
docker_image: str,
docker_image: str = "",
cidfile: Path | None = None,
run_args: Sequence[str] | None = None,
env_vars: Mapping[str, str] | None = None,
Expand Down Expand Up @@ -60,7 +60,7 @@ def build_run_command(

def build_gui_command(
case_dir: Path,
docker_image: str | None = None,
docker_image: str = "",
adapter: SolverAdapter | None = None,
) -> list[str]:
"""Build the docker command to launch the solver GUI for a case."""
Expand Down
5 changes: 3 additions & 2 deletions csauto/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def singularity_selection() -> RuntimeSelection:
raise FileNotFoundError("apptainer/singularity executable not found in PATH.")
return RuntimeSelection(
runtime=RUNTIME_SINGULARITY,
docker_image=docker_image,
docker_image="",
singularity_image=image,
singularity_bin=sbin,
)
Expand Down Expand Up @@ -154,7 +154,7 @@ def docker_selection() -> RuntimeSelection:
if resolved_native:
return RuntimeSelection(
runtime=RUNTIME_NATIVE,
docker_image=docker_image,
docker_image="",
saturne_bin=resolved_native,
)
raise FileNotFoundError(
Expand Down Expand Up @@ -280,6 +280,7 @@ def build_runtime_run_command(
cmd.append(selection.singularity_image)
cmd.append(adapter.container_bin_name)
cmd.extend(adapter.run_argv(container_case, nprocs, nt, run_args))
print(cmd)
return cmd
raise ValueError(f"Unsupported runtime: {selection.runtime}")

Expand Down
6 changes: 5 additions & 1 deletion csauto/solvers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from .base import SolverAdapter, SolverAdapterBase

DEFAULT_SOLVER = "code_saturne"
_SOLVER_NAMES = ("code_saturne", "stub")
_SOLVER_NAMES = ("code_saturne", "stub", "code_aster")


def available_solvers() -> tuple[str, ...]:
Expand All @@ -37,6 +37,10 @@ def _adapter_for(normalized: str) -> SolverAdapter:
from .stub import StubAdapter

return StubAdapter()
if normalized == "code_aster":
from .code_aster import CodeAsterAdapter

return CodeAsterAdapter()
choices = ", ".join(available_solvers())
raise ValueError(f"Unknown solver: {normalized!r}. Choices: {choices}")

Expand Down
165 changes: 165 additions & 0 deletions csauto/solvers/code_aster.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
"""Fake solver adapter used by integration tests.

Runs as a short python script (native runtime with ``saturne_bin`` pointing at
a Python interpreter) that writes ``OUT/run_0001/stub.log`` with one ``step N``
line per requested step and a final completion marker. It exercises the whole
prepare -> run -> status pipeline without any code_saturne convention on disk.
"""

from __future__ import annotations

import re
from collections.abc import Mapping, Sequence
from pathlib import Path
from typing import ClassVar

from ..execution import RUNTIME_DOCKER, RUNTIME_NATIVE, RUNTIME_SINGULARITY, RuntimeSelection, shared_dir_symlink_mounts
from ..logs import read_tail_lines
from ..registry import STATUS_DONE, STATUS_FAILED
from .base import SolverAdapterBase

CODE_ASTER_EXPORT_EXTENSION = "export"


class CodeAsterAdapter(SolverAdapterBase):
name: ClassVar[str] = "code_aster"
native_bin_name: ClassVar[str] = "run_aster"
container_bin_name: ClassVar[str] = ""
container_root: ClassVar[str] = "/home/user"
default_docker_image: ClassVar[str] = "simvia/code_aster:17.4.0"
results_dirname: ClassVar[str] = "RESU"
logs_dirname: ClassVar[str] = "LOGS"
shared_dir_names: ClassVar[tuple[str, ...]] = (
"MESH",
"RESU",
)
export_file: ClassVar[str] = "study.export"

def build_run_command(
self,
case_dir: Path,
nprocs: int,
nt: int,
selection: RuntimeSelection,
*,
cidfile: Path | None = None,
run_args: Sequence[str] | None = None,
cleanenv: bool = True,
env_vars: Mapping[str, str] | None = None,
tmp_name: str = "TMP",
) -> list[str]:
"""Build the docker command to launch a case."""
runs_root = case_dir.parent.resolve()
container_root = self.container_root
container_case = f"{container_root}/{case_dir.name}"
exportfile = str(self.export_file).split("/")[-1]
host_tmpdir = f"{runs_root}/{case_dir.name}/{tmp_name}"
solverlogpath = f"{self.results_dirname}/{self.logs_dirname}/run_solver.log"

with open(f"{runs_root}/{case_dir.name}/{exportfile}", "a") as f:
f.write(f"F mess {solverlogpath} R 6\n")

links = [f"{runs_root}:{container_root}"]
for i, shared_data in enumerate(shared_dir_symlink_mounts(runs_root, self.shared_dir_names)):
readonly = shared_data[1]
linked = f"{shared_data[0]}:{container_case}/{list(self.shared_dir_names)[i]}"
links.append(f"{linked}:ro" if readonly else f"{linked}")

# TODO: Pour relier le fichier de message temporaire
# solverlogfile = Path(f"{runs_root}/{case_dir.name}/{solverlogpath}")
# links.append(f"{runs_root}/{case_dir.name}/{solverlogpath}:{container_case}/{tmp_name}/proc.0/fort.6")

# TODO : Ajouter la suppression de tous les dossiers partagé sauf celui de resultats
rmdir = [f"{host_tmpdir}"]

if selection.runtime == RUNTIME_DOCKER:
add_cid = f"--cidfile {cidfile!s}" if cidfile else ""

bind_links = " ".join(f"-v {link}" for link in links)
removed_dirs = " ".join(f"rm -rf {dire}" for dire in rmdir)

cmd = [
"nohup",
"bash",
"-c",
f"docker run "
f"{bind_links} "
f"-w {container_case} "
f"--label csauto.case_id={case_dir.name} "
f"{add_cid} "
f"{selection.docker_image} "
f"bash -c 'source /opt/activate.sh && run_aster {exportfile} --wrkdir {container_case}/{tmp_name}/'; "
f"{removed_dirs}",
]
elif selection.runtime == RUNTIME_SINGULARITY:
host_apptainer = f"~/apptainer_tmp_{case_dir.name}"
rmdir.append(f"{host_apptainer}")

bind_links = " ".join(f"--bind {link}" for link in links)
removed_dirs = " ".join(f"rm -rf {dire}" for dire in rmdir)

cmd = [
"nohup",
"bash",
"-c",
f"mkdir -p {host_apptainer} && "
f"export APPTAINER_TMPDIR={host_apptainer} && "
f"{selection.singularity_bin} exec "
f"{bind_links} "
f"--pwd {container_case} "
f"{selection.singularity_image} "
f"bash -c 'source /opt/activate.sh && run_aster {exportfile} --wrkdir {container_case}/{tmp_name}/'; "
f"{removed_dirs}",
]
elif selection.runtime == RUNTIME_NATIVE:
# TODO
raise NotImplementedError(f"{RUNTIME_NATIVE} must be implemented for code_aster.")
else:
if selection.runtime in ["cave", "salome_meca"]:
# TODO
raise NotImplementedError(
f"{selection.runtime} could be in the roadmap of code_aster. Please contact support."
)
else:
raise NotImplementedError(f"{selection.runtime} not in the development roadmap of code_aster.")

return cmd

def run_argv(self, case_path: str | Path, nprocs: int, nt: int, run_args: Sequence[str] | None = None) -> list[str]:
return [""]

def find_setup_file(self, template_dir: Path) -> Path:
files = list(Path(template_dir).glob(f"*.{CODE_ASTER_EXPORT_EXTENSION}"))
if len(files) == 1 and files[0].is_file():
self.export_file = files[0]
return files[0]
raise FileNotFoundError(f".export file not found in template: {template_dir}")

def detect_outcome(self, case_dir: Path, start_time: str | None = None) -> str | None:
success_patterns = [
re.compile(r"DIAGNOSTIC JOB : OK", re.IGNORECASE),
re.compile(r"DIAGNOSTIC JOB : <A>_ALARM", re.IGNORECASE),
]
failure_patterns = [
re.compile(r"DIAGNOSTIC JOB : <F>_ABNORMAL_ABORT", re.IGNORECASE),
re.compile(r"DIAGNOSTIC JOB : <F>_SYNTAX_ERROR", re.IGNORECASE),
re.compile(r"DIAGNOSTIC JOB : <S>_MEMORY_ERROR", re.IGNORECASE),
re.compile(r"DIAGNOSTIC JOB : <S>_NO_CONVERGENCE", re.IGNORECASE),
re.compile(r"DIAGNOSTIC JOB : <S>_CPU_LIMIT", re.IGNORECASE),
re.compile(r"DIAGNOSTIC JOB : <S>_ERROR", re.IGNORECASE),
re.compile(r"DIAGNOSTIC JOB : NO_TEST_RESU", re.IGNORECASE),
re.compile(r"DIAGNOSTIC JOB : NOOK_TEST_RESU", re.IGNORECASE),
]
files = list(Path(f"{case_dir}/{self.results_dirname}/{self.logs_dirname}/").glob("run_solver.log"))
if len(files) == 0:
return None
elif len(files) == 1:
lines = read_tail_lines(files[0], lines=40)
joined = "\n".join(lines)
if any(p.search(joined) for p in success_patterns):
return STATUS_DONE
if any(p.search(joined) for p in failure_patterns):
return STATUS_FAILED
else:
raise
return None
112 changes: 112 additions & 0 deletions examples/codeaster-cube/MESH/create_mesh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env python

###
### This file is generated automatically by SALOME v9.15.0 with dump python functionality
###

import salome
import SMESH
from salome.geom import geomBuilder
from salome.smesh import smeshBuilder

salome.salome_init()

###
### GEOM component
###

geompy = geomBuilder.New()

Origin = geompy.MakeVertex(0, 0, 0)
OX = geompy.MakeVectorDXDYDZ(1, 0, 0)
OY = geompy.MakeVectorDXDYDZ(0, 1, 0)
OZ = geompy.MakeVectorDXDYDZ(0, 0, 1)
box = geompy.MakeBoxDXDYDZ(1, 1, 1)
x_0 = geompy.CreateGroup(box, geompy.ShapeType["FACE"])
geompy.UnionIDs(x_0, [3])
x_H = geompy.CreateGroup(box, geompy.ShapeType["FACE"])
geompy.UnionIDs(x_H, [13])
y_0 = geompy.CreateGroup(box, geompy.ShapeType["FACE"])
geompy.UnionIDs(y_0, [23])
y_H = geompy.CreateGroup(box, geompy.ShapeType["FACE"])
geompy.UnionIDs(y_H, [27])
z_0 = geompy.CreateGroup(box, geompy.ShapeType["FACE"])
geompy.UnionIDs(z_0, [31])
z_H = geompy.CreateGroup(box, geompy.ShapeType["FACE"])
geompy.UnionIDs(z_H, [33])
all_edges = geompy.CreateGroup(box, geompy.ShapeType["EDGE"])
geompy.UnionIDs(all_edges, [5, 8, 10, 12, 15, 18, 20, 22, 25, 26, 29, 30])
[x_0, x_H, y_0, y_H, z_0, z_H, all_edges] = geompy.GetExistingSubObjects(box, False)
Auto_group_for_Sub_mesh_1 = geompy.CreateGroup(box, geompy.ShapeType["FACE"])
geompy.UnionList(Auto_group_for_Sub_mesh_1, [x_0, x_H, y_0, y_H, z_0, z_H])
geompy.addToStudy(Origin, "O")
geompy.addToStudy(OX, "OX")
geompy.addToStudy(OY, "OY")
geompy.addToStudy(OZ, "OZ")
geompy.addToStudy(box, "box")
geompy.addToStudyInFather(box, x_0, "x=0")
geompy.addToStudyInFather(box, x_H, "x=H")
geompy.addToStudyInFather(box, y_0, "y=0")
geompy.addToStudyInFather(box, y_H, "y=H")
geompy.addToStudyInFather(box, z_0, "z=0")
geompy.addToStudyInFather(box, z_H, "z=H")
geompy.addToStudyInFather(box, all_edges, "all_edges")
geompy.addToStudyInFather(box, Auto_group_for_Sub_mesh_1, "Auto_group_for_Sub-mesh_1")

###
### SMESH component
###

smesh = smeshBuilder.New()

Mesh_1 = smesh.Mesh(box, "Mesh_1")
NETGEN_1D_2D_3D = Mesh_1.Tetrahedron(algo=smeshBuilder.NETGEN_1D2D3D)
NETGEN_3D_Parameters_1 = NETGEN_1D_2D_3D.Parameters()
NETGEN_3D_Parameters_1.SetMaxSize(0.173205)
NETGEN_3D_Parameters_1.SetMinSize(0.00173205)
NETGEN_3D_Parameters_1.SetSecondOrder(0)
NETGEN_3D_Parameters_1.SetOptimize(1)
NETGEN_3D_Parameters_1.SetFineness(2)
NETGEN_3D_Parameters_1.SetChordalError(-1)
NETGEN_3D_Parameters_1.SetChordalErrorEnabled(0)
NETGEN_3D_Parameters_1.SetUseSurfaceCurvature(1)
NETGEN_3D_Parameters_1.SetFuseEdges(1)
NETGEN_3D_Parameters_1.SetQuadAllowed(1)
NETGEN_3D_Parameters_1.SetCheckChartBoundary(8)
x_0_1 = Mesh_1.GroupOnGeom(x_0, "x=0", SMESH.FACE)
x_H_1 = Mesh_1.GroupOnGeom(x_H, "x=H", SMESH.FACE)
y_0_1 = Mesh_1.GroupOnGeom(y_0, "y=0", SMESH.FACE)
y_H_1 = Mesh_1.GroupOnGeom(y_H, "y=H", SMESH.FACE)
z_0_1 = Mesh_1.GroupOnGeom(z_0, "z=0", SMESH.FACE)
z_H_1 = Mesh_1.GroupOnGeom(z_H, "z=H", SMESH.FACE)
all_edges_1 = Mesh_1.GroupOnGeom(all_edges, "all_edges", SMESH.EDGE)
Quadrangle_2D = Mesh_1.Quadrangle(algo=smeshBuilder.QUADRANGLE, geom=Auto_group_for_Sub_mesh_1)
Quadrangle_Parameters_1 = Quadrangle_2D.QuadrangleParameters(smeshBuilder.QUAD_QUADRANGLE_PREF, -1, [], [])
Regular_1D = Mesh_1.Segment(geom=all_edges)
Number_of_Segments_1 = Regular_1D.NumberOfSegments(10)
isDone = Mesh_1.Compute()
Mesh_1.CheckCompute()
[x_0_1, x_H_1, y_0_1, y_H_1, z_0_1, z_H_1, all_edges_1] = Mesh_1.GetGroups()
box_1 = Mesh_1.GroupOnGeom(box, "box", SMESH.VOLUME)
Sub_mesh_1 = Quadrangle_2D.GetSubMesh()
Sub_mesh_2 = Regular_1D.GetSubMesh()


## Set names of Mesh objects
smesh.SetName(Sub_mesh_1, "Sub-mesh_1")
smesh.SetName(box_1, "box")
smesh.SetName(NETGEN_3D_Parameters_1, "NETGEN 3D Parameters_1")
smesh.SetName(y_H_1, "y=H")
smesh.SetName(z_0_1, "z=0")
smesh.SetName(Mesh_1.GetMesh(), "Mesh_1")
smesh.SetName(Quadrangle_Parameters_1, "Quadrangle Parameters_1")
smesh.SetName(x_0_1, "x=0")
smesh.SetName(x_H_1, "x=H")
smesh.SetName(Sub_mesh_2, "Sub-mesh_2")
smesh.SetName(z_H_1, "z=H")
smesh.SetName(all_edges_1, "all_edges")
smesh.SetName(Regular_1D.GetAlgorithm(), "Regular_1D")
smesh.SetName(y_0_1, "y=0")
smesh.SetName(NETGEN_1D_2D_3D.GetAlgorithm(), "NETGEN 1D-2D-3D")
smesh.SetName(Number_of_Segments_1, "Number of Segments_1")
smesh.SetName(Quadrangle_2D.GetAlgorithm(), "Quadrangle_2D")
Binary file added examples/codeaster-cube/MESH/mesh1.med
Binary file not shown.
Binary file added examples/codeaster-cube/MESH/mesh2.med
Binary file not shown.
Loading
Loading