Piping in constraint environment
1 - Introduction
The objective of this tutorial is to generate and optimize a pipe that is in a constraint environment. Indeed, we will force the pipe to pass through 4 points and force its neutral fiber minimal radius to be superior than an imposed value.
2 - Build the bot in 4 steps
Before beginning, you have access to tutorial5 in the tutorials
folder cloned as explained in the first tutorial. We will apply DessIA method to find the best solution to our problem.
2.1 - Describe your engineering system
2.1.1 - Constraint environment
First of all, we need to import some packages :
import volmdlr as vm
import volmdlr.faces as vmf
import volmdlr.primitives3d as p3d
from dessia_common import DessiaObject
from typing import List
In our problem, we want to generate and optimize solutions regarding a constraint environment. In this case, we want to install a pipe with a housing, a support. We define this environment with Housing method that needs a list of faces (the environment) and the spatial environment origin.
class Housing(DessiaObject):
_standalone_in_db = False
def __init__(self, faces: List[vmf.Face3D], origin: vm.Point3D,
name: str = ''):
self.origin = origin
self.faces = faces
DessiaObject.__init__(self, name=name)
def volmdlr_primitives(self): #thanks to this method you could display 3D
for face in self.faces:
face.translation(self.origin, copy=False)
return self.faces
How to create a face ?
Faces are created thanks to volmdlr which will be called thanks to 'vm.'.
- Create a frame :
frame_center = vm.Point3D(0, 1, 0) #x,y,z position
dir1, dir2, dir3 = vm.Vector3D(1,0,0), vm.Vector3D(0,1,0), vm.Vector3D(0,0,1) #vector's direction
frame = vm.Frame3D(frame_center, dir1, dir2, dir3)
- Create a plane where the face will be :
plane = vm.faces.Plane3D(frame) #from vm.faces we create a plane thanks to frame. Plane will be based on dir1 and dir2
- Create a Surface2D :
out_contour = vm.wires.ClosedPolygon2D([vm.Point2D(-1, -1), vm.Point2D(1, -1), vm.Point2D(1, 1), vm.Point2D(-1, 1)])
surface2d = vm.faces.Surface2D(outer_contour=out_contour, inner_contours=[]) #Our contour doesn't have hole this is why inner_contours=[]
- Create a face :
planeface = vm.faces.PlaneFace3D(surface3d=plane, surface2d=surface2d) #We put the surface2d on the plane and we have 3D surface
- 3D display is available if your class contains a 'volmdlr_primitives' method in it. This method has to return volmdlr elements.
planeface.babylonjs() #if you want to display planeface created
By typing the following code, our constraint environment appears:
import tutorials.tutorial5_piping as tuto
import plot_data.core as plot_data
import volmdlr as vm
f1 = vm.Frame3D(vm.Point3D(0.05, 0.1, 0), vm.Vector3D(1, 0, 0), vm.Vector3D(0, 1, 0), vm.Vector3D(0, 0, 1))
p1 = vm.faces.Plane3D(f1)
s1 = vm.faces.Surface2D(outer_contour=vm.wires.ClosedPolygon2D([vm.Point2D(-0.05, -0.1),
vm.Point2D(0.05, -0.1),
vm.Point2D(0.05, 0.1),
vm.Point2D(-0.05, 0.1)]),
inner_contours=[])
face1 = vm.faces.OpenShell3D([vm.faces.PlaneFace3D(surface3d=p1, surface2d=s1)])
face1.color = (92/255, 124/255, 172/255)
face1.alpha = 1
f2 = vm.Frame3D(vm.Point3D(0.05, 0.1, 0.005), vm.Vector3D(1, 0, 0), vm.Vector3D(0, 0, 1), vm.Vector3D(0, 1, 0))
p2 = vm.faces.Plane3D(f2)
s2 = vm.faces.Surface2D(outer_contour=vm.wires.ClosedPolygon2D([vm.Point2D(-0.05, -0.005),
vm.Point2D(0.05, -0.005),
vm.Point2D(0.05, 0.005),
vm.Point2D(-0.05, 0.005)]),
inner_contours=[])
face2 = vm.faces.OpenShell3D([vm.faces.PlaneFace3D(surface3d=p2, surface2d=s2)])
face2.color = (92/255, 124/255, 172/255)
face2.alpha = 1
housing = tuto.Housing(faces=[face1, face2], origin=vm.Point3D(0, 0, 0))
housing.babylonjs()
2.1.2 - Neutral fiber
Next step, we need to build the neutral fiber of our pipe. For that, we will create Frame class that will be those 'Checkpoint' for the pipe.
How to create a Frame
A Frame will characterize the path of our pipe. It needs a start and an end. In our case, there will be 4 Frames.
p_start = vm.Point3D(0, 0.1, 0.01) #x,y,z position
p_end = vm.Point3D(0.05, 0.1, 0.01)
frame1 = tuto.Frame(start = p_start_, end = p_end)
2.1.3 - Pipe
To finish the problem description, we need to instantiate a Piping class.
How to create a Piping
A Piping will be characterized by simple elements that will be presented below.
p_start = vm.Point3D(0, 0, 0) #x,y,z position, start
p_end = vm.Point3D(0.1, 0, 0) #end
dir_start = vm.Vector3D(0, 0, 1) #pipe start normal
dir_end = vm.Vector3D(1, 0, 1) #pipe end normal
pipe_diam = 0.005 #pipe diameter
connect_length = 0.1 #what distance do you want start and end to follow their normal to a potential connector.
minimum_radius = 0.03 #minimum radius of curvature allowed in the neutral fiber
piping1 = tuto.Piping(start=p_start, end=p_end,
direction_start=dir_start, direction_end=dir_end,
diameter=pipe_diam, length_connector=connect_length, minimum_radius=minimum_radius)
2.2 - Define engineering simulations needed
Once our basic classes created, we have to put them together and create a solution that will be optimized. For that, we implement an Assembly class.
How to create an Assembly
Thanks to elements created before, we can define an Assembly.
assembly1 = tuto.Assembly(frames=[frame1, frame3, frame4, frame2], piping=piping1, housing=housing)
This class will be used to find the optimum thanks to optimizer.
2.3 - Create elementary generator & optimizer
To build our optimizer, we need to import some packages :
from random import random
import cma
In Optimize class, we would like to explain to you the aim of the Optimize method. At each line, there is a comment if needed.
- Concerning 'cma.fmin', this function will find the best solution using 'self.objective' and an initialized vector solution. The method uses a vector that refers to position of point in order to respect condition concerning minimal radius.
def optimize(self, assemblies: List[Assembly], number_solution_per_assembly:int)->List[Assembly]:
solutions = []
for assembly in assemblies: #we would find every best solutions concerning a list of assembly
self.assembly = assembly
x0a = [random() for i in range(len(self.assembly.frames))] #First vector to initialize solving
check =True
compt = 0
number_solution = 0
while check:
xra, fx = cma.fmin(self.objective, x0a, 0.1,
options={'bounds': [0, 1], #each point can be placed thanks to a percentage and the direction of each frame
'tolfun': 1e-8, #precision of each solution returned by self.objective
'verbose': 10,
'ftarget': 1e-8, #if self.objective return is smaller than 'ftarget', a solution is found
'maxiter': 10})[0:2] #maximum iteration wanted per searching, it chooses the best solution
waypoints = self.assembly.waypoints
radius = self.assembly.piping.genere_neutral_fiber(waypoints).radius #all radius characterizing neutral fiber
min_radius = min(list(radius.values())) #we accept the solution with a 10% tolerance
if min_radius >= 0.9*self.assembly.piping.minimum_radius and len(list(radius.keys())) == len(waypoints) - 2:
new_assembly = self.assembly.copy()
new_assembly.update(xra)
solutions.append(new_assembly)
compt += 1
number_solution += 1
if compt == 20 or number_solution_per_assembly == number_solution:
break
return solutions
def objective(self, x):
objective = 0
self.update(x) #update assembly by a potential solution
waypoints = self.assembly.waypoints
radius = self.assembly.piping.genere_neutral_fiber(waypoints).radius
min_radius = min(list(radius.values()))
if min_radius < self.assembly.piping.minimum_radius:
objective += 10 + (min_radius - self.assembly.piping.minimum_radius)**2 #penalization if a radius is smaller that expected
else:
objective += 10 - 0.1*(min_radius - self.assembly.piping.minimum_radius)
return objective
def update(self, x):
self.assembly.update(x)
If you have more question concerning this part, we recommend you to read Simple power-transmission optimization.
2.4 - Build your workflow
First of all, in a workflow.py file, you have to create all assemblies you want to optimize. Each assembly will have something different, for example minimum radius allowed. Do not forget to import packages recommended :
import tutorials.tutorial5_piping as tuto
import volmdlr as vm
import dessia_common.workflow as wf
Once done, we will begin the conscrution of workflow.
- Transform Optimizer object into workflow blocks.
import dessia_common.workflow as wf
block_optimizer = wf.InstanciateModel(tuto.Optimizer, name='Optimizer')
method_optimize = wf.MethodType(class_=tuto.Optimizer, name='optimize')
block_optimize = wf.ModelMethod(method_type=method_optimize, name='optimize')
- In our case, elements are already generated and given to the optimizer. Now, we want to display in the platform some elements. Once elements generated in platform, we could see them thanks to a ParallelPlot.
list_attribute1 = ['length', 'min_radius', 'max_radius', 'distance_input', 'straight_line']
display_reductor = wf.ParallelPlot(list_attribute1, 1, name='Display')
- We have all what we need, let's create the workflow.
block_workflow = [block_optimizer, block_optimize, display_reductor] #task order in workflow
pipe_worflow = [wf.Pipe(block_optimizer.outputs[0], block_optimize.inputs[0]), #connection in workflow
wf.Pipe(block_optimize.outputs[0], display_reductor.inputs[0])]
workflow = wf.Workflow(block_workflow, pipe_worflow, block_optimize.outputs[0]) #last element refers to solutions
input_values = {workflow.index(block_optimize.inputs[1]): assemblies,
workflow.index(block_optimize.inputs[2]): 1,
}
Workflow in your platform
To put your workflow in your platform, you have to type the following command after your workflow :
from dessia_api_client import Client
c = Client(api_url='https://api.YOURPLATFORM.dessia.tech')
r = c.create_object_from_python_object(workflow_run)