The aim of this section is to provide an exhaustive description of the capabilities of the platform while using the OpenPisco Domain Specific languages (DSLs) interface.
How do OpenPisco and OpenPiscoCL work?
When running the OpenPiscoCL module using the command line, the user is expected to provide as an input (within the command line) a file written in a domain specific syntax (see the tutorial for actual examples).
Then, OpenPiscoCL simply does:
Reading the file
Parsing the information contained within the script
Run each operation, mapped to the actual implementation, one by one.
One DSL supported within both applications is a xml-like idiom (denoted by xml). The language relies on tag-like entities.
At this point, we provide below some elementary rules:
Each elementary operation performed during a topology optimization with level set can be use with this API. That includes in particular the physical solver and the remeshing related tools.
It is not required to build a complete optimization problem to use this API, it is just as modular as the platform itself
It aims to be even more user-friendly than the python API and should be enough for a user
Within the script, the module tags order do not matter, meaning you can for instance start with the Zones description before even characterizing the domain
Within a module tag, only the Action operations order matters, as it determines the sequential intructions executed by OpenPiscoCL
Elementary mathematical operations (+,-,*,/) are supported within a string containing numbers
Module tags
In each case, we provide a brief description of the module and the features available. For clarity and completeness, we also provide a template-like snippet in xml.
In each xml snippet provided below, a representative variable name for the argument is given followed by “:” and the type of the variable expected (similar to type hinting logic in python).
Therefore
<ModuleType>
<ModuleSubType
id="moduleTypeId:int"
origin="originValX:float originValY:float originValZ:float"
path="moduleTypePath:str"/>
</ModuleType>
means that we expect
an integer moduleTypeId for id
3 floats values for origin, respectively originValX,originValY and originValZ
a string moduleTypePath for path
When there are several options available, it is denoted by
<ModuleType>
<ModuleSubType
type="[type1|type2|type3]"
use="*None|useId:int" />
</ModuleType>
which means:
you can chose either type1, type2 or type3 for type
you can define a useId for use, if you don’t, the default option is None
Finally, when the type is meant to be related to a specific class instance/function of the platform, it is denoted as OpenPisco*
<ModuleType>
<ModuleSubType
criteria="criteria:OpenPiscoCriteria"/>
</ModuleType>
In other words:
It is not a simple primitive (string, int, float, bool)
The user has to refer to the proper documentation to check which xml tags are available within a module
There are a limited number of choices and an exhaustive description is out of the scope of the present documentation
Plugins
Enable to import a python module “on the fly” from the module name. For instance, the following code
<Plugins>
<Plugin name="MyAwesomeRepo.MyAwesomeModuleWithinRepo" />
</Plugins>
would be equivalent in python to
import MyAwesomeRepo.MyAwesomeModuleWithinRepo
By doing so, it is even possible to enable the use of one’s own routine without modifying the platform source code.
Mesh
There are two possibilities. You can create a 3D structured grid with the platform
<Grids>
<Grid
id="gridId:int"
n="numElemX:int numElemY:int numElemZ:int"
origin="originX:float originY:float originZ:float"
length="lengthX:float lengthY:float lengthZ:float"/>
</Grids>
where the grid is labeled with an integer id and n denotes the number of elements in each direction.
Origin and length are respectively the position of the origin in cartesian coordinates and the size of the grid in each direction.
It is also possible to simply import the mesh in any format supported by the platform
(for an exhaustive list, we refer to the :py:module:`Muscat.IO.IOFactory`)
<Grids>
<Mesh
id="meshId:int"
filename="filename:(str+"."+PiscoIOFormat)" />
</Grids>
where the mesh is also labeled with an integer id.
In that case, the platform assumes the user provide a filename extension (for instance .mesh, .med etc…) consistent with the actual mesh format used within the meshfile.
Zones
This is where the implicit zones are defined.
The format supports :py:module:`Muscat.ImplicitGeometry.ImplicitGeometryObjects` and :py:module:`Muscat.ImplicitGeometry.ImplicitGeometryOperators`
Each are endowed with an integer id, so that any of them can be used by other bricks.
These operations and their respective arguments match almost exactly the ImplicitGeometry objects and operators name in the python API within Muscat
Albeit a mapping between the name of the implemented features and its actual use within the xml format is not provided here, it is not difficult to find it,
should you ever need to use one not present in the tutorials.
As a matter of fact; in the python implementation, all the implicit geometry objects and operators
classes are followed by a call to a :py:module:`Muscat.ImplicitGeometry.ImplicitGeometryFactory.RegisterClass`. The first argument is a string that can be used in xml.
We provide below an example where we define:
- 2 implicit geometry objects
-
- 1 operator:
-
<Zones>
<Plane
id="planeId:int"
point="pointCoordX:float pointCoordY:float pointCoordZ:float"
normal="normalX:float normalY:float normalZ:float"
desc="planeDescription:str"/>
<Cylinder
id="cylinderId:int"
center1="center1CoordX:float center1CoordY:float center1CoordZ:float"
center2="center2CoordX:float center2CoordY:float center2CoordZ:float"
radius="radiusVal:float"
desc="cylinderDescription:str"/>
<Union
id="unionZoneId:int"
z="zoneIdToMerge1:int zoneIdToMerge2:int"/>
</Zones>
As a common features, each implicit geometry object/operators are labeled with an id.
Those ids are already explicitly used for union, to create a new implicit geometry from the implicit geometries labeled as zoneIdToMerge1 and zoneIdToMerge2.
Here, in this specific case, it would be planeId and cylinderId, for instance.
Level sets
Then, we define the level sets, each endowed with:
Their support described by the mesh id
Their own id, in order to be used by other xml tags/platform bricks
<LevelSets>
<LevelSet
id="levelSetId:int"
support="supportId:int"/>
</LevelSets>
Physical analysis
Next, we define the physical problems.
For the static_elastic physic, a template example is presented below
<PhysicalProblems>
<[GeneralAster|GeneralFreefem|GeneralZset|StructuredFEA]
id="physicalProblemId:int"
type="static_elastic"
p="*1|2" >
<Material
eTag="*everyelement|elementsTag:str"
E="youngModulusVal:float"
Nu="poisonRatioVal:float"/>
<Dirichlet
eTag="elementsTag:str"
dofs="[0|1|2|0 1|0 2|1 2|0 1 2]"
value="[prescribedValue:float]"/>
<Force
eTag="elementsTag:str"
value="FxVal:float FyVal:float FzVal:float"/>
<Pressure
eTag="elementsTag:str"
value="pressureVal:float"/>
<[/GeneralAster|/GeneralFreefem|/GeneralZset|/StructuredFEA]>
</PhysicalProblems>
Each problem includes:
the physical solver to use, amongst those supported by the platform
id, the id for the physical problem
type, the type of physical analysis involved (assuming the chosen physical solver can handle it, see PhysicalSolver.rst for that)
p, the degree of the finite element approximation (either linear with p=1 or quadratic with p=2)
the parameters related to the physical analysis chosen, in linear elasticity that would be the material properties denoted by the xml tag Material. The optional elementsTag is available to enable multi materials computations.
the boundary conditions; amongst them there are
Dirichlet condition: Displacement prescribed at a given value on given degrees of freedom (dof) on a zone given by an eTag
Neumann condition: Apply a force on a zone given by an elementsTag
Pressure condition: Apply a pressure on a zone given by an elementsTag
We provide below a concrete example relying on the interface with Code_Aster
<PhysicalProblems>
<GeneralAster type="static_elastic">
<Material E="210000000000" Nu="0.3" />
<Dirichlet eTag="ET1" dofs="0 1 2" value="0.0"/>
<Dirichlet eTag="ET2" dofs="0" value="0.0"/>
<Dirichlet eTag="ET7" dofs="2" value="0.0"/>
<Force eTag="ET3" value="0. -10000. 0." />
</GeneralAster>
</PhysicalProblems>
where ET1, ET2 and ET7 are elementsTag defined within the domain, prior to running the physical problem.
Therefore, in this example, these elements tag have to created (see Action section below for more details) if they do not already exist in the mesh.
Optimization problems
We now define the actual optimization problems, relying on most of the already introduced existing module tags.
A generic template code would be
<OptimProblems>
<OptimProblem
type="[PenalizedTopoGeneric|SquaredTopoGeneric|TopoGeneric]"
id="optimProblemId:int"
OnZone="onZoneId:int" Dirichlet="dirichletId:int" Neumann="*None|neumannId:int"
useLevelset="levelSetId:int"
e2="[*MinLenghtScaleInitMesh|regularizationCoeffVal:float]"
output="outputId:int">
<Objective
type="Criteria1:PiscoCriteria"
name="criteria1Name:str"
useProblem="*None|criteria1ProblemId:int" />
<Constraint
type="Criteria2:PiscoCriteria"
name="criteria2Name:str"
useProblem="*None|criteria2ProblemId:int"
upperBound="upperBoundVal:float"/>
<Constraint
type="Criteria3:PiscoCriteria"
name="criteri3Name:str"
useProblem="*None|criteria3ProblemId:int"
upperBound="upperBoundVal:float"/>
...
</OptimProblem>
</OptimProblems>
For each optimization problem, we have
type, the type of optimization problem
id, the id for the optimization problem
OnZone, the id associated to the non optimizable zone
Dirichlet, the id associated to the dirichlet zone
Neumann, the id associated to the neumann zone (optional)
useLevelset, the considered level set id
e2, the regularization coefficient value (optional). The default value is the minimal characteristic size of the element computed on the initial mesh.
output, the id associated the output (see output tag section below for more details)
- an objective, described by:
-
- None/one/more than one constraint(s), with
their respective upperbounds
criteria supported by the platform
criteria name
the physical problems associated (if applicable)
Note also that, even though they are called Dirichlet and Neumann in the API, the real purpose is to make sure there are no deconnection between these 2 zones. Otherwise, the problem would not make sense from a physical point of view.
Note that for a physical criterion, whether it is the objective or a constraint, one also has to specify problem associated.
That is not the case for a geometrical criterion.
Optimization algorithms
Each optimization problem can be handled by a different algorithm, if required. We provide below the xml template for the so-called Nullspace algorithm
<TopologicalOptimizations>
<OptimAlgoNullSpace
id="optimAlgoId:int"
useOptimProblem="optimProblemId:int"
numberOfDesignStepsMax="[*200|maxDesignStepsAllowed:int]"
numberOfIterationsMax="[*20|maxTrialStepsAllowed:int]"
stepSizeMax="[*1|stepSizeMaxVal:float]"
stepSize="[*1|stepSizeVal:float]"
alphaC="[*1.0|alphaCVal:float]"
alphaJ="[*0.5|alphaJVal:float]"
tol_merit="[*0.0005,tolMeritVal:float]"/>
</TopologicalOptimizations>
Where there are:
the algorithm name (here, OptimAlgoNullSpace)
id, an algorithm labeled with an integer id
useOptimProblem, the id related to the optimization problem to apply the algo on
numberOfDesignStepsMax, the maximal number of design steps allowed (optional)
numberOfIterationsMax, number of trials until the merit function decreases within a design step (optional)
stepSizeMax, the maximal step size allowed for the optimization (optional)
stepSize, the initial step size along the descent direction (optional)
- Some considerations specific to the NullSpace algorithm, related in particular to the merit function considered to evaluate a trial
alphaC and alphaJ, the coefficients related to the merit function (optional)
tol_merit, the tolerance for acceptance step with the merit function method (optional)
Outputs
At this point, it is possible to define the desired outputs format.
For an exhaustive list of the formats available, we refer to the Muscat.IO.IOFactory()
.
<Outputs>
<Output id="outputId:int" name="Filename:(str+"."+PiscoIOFormat)"/>
</Outputs>
Guessing from the filename extension provided by the user in Filename, the platform tries to write results in the associated format.
Still, notes that not all format are equivalent regarding their storing capabilities.
For instance, if you want to be able to save all the intermediate design steps within the optimization process, not all formats would fit your needs.
For this very reason, one format used classicaly to handle temporal data is the .xdmf format (also compatible with Paraview).
Another interesting feature is the ProxyWriter (PROXY); we provide below a concrete example to illustrate its use
<Outputs>
<Output id="1" name="LBeamHistory.xdmf" inTempDirectory="False" />
<Output id="2" name="LBeamHistory.csv" inTempDirectory="False" />
<Output id="3" name="PROXY" outputs="1 2"/>
</Outputs>
In this context, it can be used for instance to write the same information in 2 different formats using a single output id.
Actions
What the action actually do is encapsulated in the Apply method of each action class within each submodule of :py:module:`OpenPisco.Actions`.
General:
Unstructured:
CreateTagsFromZones
AddETagFromNTag
AddNodalTag
Remesh
CleanLonelyNodes
Create0DElements
Fields:
Optimization algorithm
Mesh Actions
WriteToFile
SaveShapeToFile
SetAllToInside
AddSkin
ETagFromNTag
AddEdges
SetAsRequired
CleanTags
CleanInternalFaces
DeleteZone
MirrorLevelSet