├── Job-1.inp ├── README.txt ├── TripleJunctionTest.cae ├── TripleJunctionTest.jnl ├── UEL.for ├── UMAT.for ├── __init__.py ├── abaqusparser.py ├── cohelement.py ├── element.py ├── interfelement.py ├── interfnode.py ├── main.py ├── mesh.py ├── newmesh.py ├── node.py └── twinnode.py /README.txt: -------------------------------------------------------------------------------- 1 | Nicolo Grilli 2 | University of Oxford 3 | AWE project 2020 4 | 23 Giugno 2020 5 | 6 | Code to generate cohesive elements 7 | at the grain boundaries in 3D 8 | 9 | Copy 'Job-1.inp' input file in this folder 10 | Elset must be named 'GRAIN1', 'GRAIN2', et cetera 11 | 12 | Run 13 | python3.6 main.py 14 | 15 | Old bulk element list from abaqus input file will be in: 16 | 'Job-1-bulk-elems.inp' 17 | 18 | Old node list from abaqus input file will be in: 19 | 'Job-1-node.inp' 20 | 21 | Old element sets with the generate keyword will be in: 22 | 'Job-1-elset.inp' 23 | 24 | New node list will be in file: 25 | 'Job-1-node-new.inp' 26 | 27 | New bulk element list will be in file: 28 | 'Job-1-elems-new.inp' 29 | 30 | Interface (zero thickness) element list will be in file: 31 | 'Job-1-int-elems.inp' 32 | 33 | Maximum triple junctions are handled 34 | A node cannot belong to more than 3 grains 35 | Only C3D8 elements are handled 36 | It is fundamental that nodes at the grain boundary 37 | belongs to bulk elements in two/three grains 38 | and are not already duplicated 39 | 40 | -------------------------------------------------------------------------------- /TripleJunctionTest.cae: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngrilli/PyCiGen/2caf21e2c173ea44e3892524917bfa12482242bb/TripleJunctionTest.cae -------------------------------------------------------------------------------- /TripleJunctionTest.jnl: -------------------------------------------------------------------------------- 1 | # -*- coding: mbcs -*- 2 | from part import * 3 | from material import * 4 | from section import * 5 | from assembly import * 6 | from step import * 7 | from interaction import * 8 | from load import * 9 | from mesh import * 10 | from optimization import * 11 | from job import * 12 | from sketch import * 13 | from visualization import * 14 | from connectorBehavior import * 15 | mdb.models['Model-1'].ConstrainedSketch(name='__profile__', sheetSize=200.0) 16 | mdb.models['Model-1'].sketches['__profile__'].rectangle(point1=(0.0, 0.0), 17 | point2=(10.0, 10.0)) 18 | mdb.models['Model-1'].Part(dimensionality=THREE_D, name='Part-1', type= 19 | DEFORMABLE_BODY) 20 | mdb.models['Model-1'].parts['Part-1'].BaseSolidExtrude(depth=0.5, sketch= 21 | mdb.models['Model-1'].sketches['__profile__']) 22 | del mdb.models['Model-1'].sketches['__profile__'] 23 | mdb.models['Model-1'].Material(name='GRAIN1') 24 | mdb.models['Model-1'].materials['GRAIN1'].Depvar(n=125) 25 | mdb.models['Model-1'].materials['GRAIN1'].UserMaterial(mechanicalConstants=( 26 | 5.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0)) 27 | mdb.models['Model-1'].Material(name='GRAIN2') 28 | mdb.models['Model-1'].materials['GRAIN2'].Depvar(n=125) 29 | mdb.models['Model-1'].materials['GRAIN2'].UserMaterial(mechanicalConstants=( 30 | 5.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 2.0)) 31 | mdb.models['Model-1'].Material(name='GRAIN3') 32 | mdb.models['Model-1'].materials['GRAIN3'].Depvar(n=125) 33 | mdb.models['Model-1'].materials['GRAIN3'].UserMaterial(mechanicalConstants=( 34 | 5.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 3.0)) 35 | mdb.models['Model-1'].HomogeneousSolidSection(material='GRAIN1', name='GRAIN1', 36 | thickness=None) 37 | mdb.models['Model-1'].HomogeneousSolidSection(material='GRAIN2', name='GRAIN2', 38 | thickness=None) 39 | mdb.models['Model-1'].HomogeneousSolidSection(material='GRAIN3', name='GRAIN3', 40 | thickness=None) 41 | mdb.models['Model-1'].parts['Part-1'].DatumPointByCoordinate(coords=(5.0, 5.0, 42 | 0.5)) 43 | mdb.models['Model-1'].ConstrainedSketch(gridSpacing=0.7, name='__profile__', 44 | sheetSize=28.3, transform= 45 | mdb.models['Model-1'].parts['Part-1'].MakeSketchTransform( 46 | sketchPlane=mdb.models['Model-1'].parts['Part-1'].faces[4], 47 | sketchPlaneSide=SIDE1, 48 | sketchUpEdge=mdb.models['Model-1'].parts['Part-1'].edges[4], 49 | sketchOrientation=RIGHT, origin=(5.0, 5.0, 0.5))) 50 | mdb.models['Model-1'].parts['Part-1'].projectReferencesOntoSketch(filter= 51 | COPLANAR_EDGES, sketch=mdb.models['Model-1'].sketches['__profile__']) 52 | mdb.models['Model-1'].sketches['__profile__'].Line(point1=(0.0, 0.0), point2=( 53 | 5.0, 0.0)) 54 | mdb.models['Model-1'].sketches['__profile__'].HorizontalConstraint( 55 | addUndoState=False, entity= 56 | mdb.models['Model-1'].sketches['__profile__'].geometry[6]) 57 | mdb.models['Model-1'].sketches['__profile__'].CoincidentConstraint( 58 | addUndoState=False, entity1= 59 | mdb.models['Model-1'].sketches['__profile__'].vertices[6], entity2= 60 | mdb.models['Model-1'].sketches['__profile__'].geometry[3]) 61 | mdb.models['Model-1'].sketches['__profile__'].EqualDistanceConstraint( 62 | addUndoState=False, entity1= 63 | mdb.models['Model-1'].sketches['__profile__'].vertices[1], entity2= 64 | mdb.models['Model-1'].sketches['__profile__'].vertices[2], midpoint= 65 | mdb.models['Model-1'].sketches['__profile__'].vertices[6]) 66 | mdb.models['Model-1'].parts['Part-1'].PartitionFaceBySketch(faces= 67 | mdb.models['Model-1'].parts['Part-1'].faces.getSequenceFromMask(('[#10 ]', 68 | ), ), sketch=mdb.models['Model-1'].sketches['__profile__'], sketchUpEdge= 69 | mdb.models['Model-1'].parts['Part-1'].edges[4]) 70 | del mdb.models['Model-1'].sketches['__profile__'] 71 | mdb.models['Model-1'].ConstrainedSketch(gridSpacing=0.7, name='__profile__', 72 | sheetSize=28.28, transform= 73 | mdb.models['Model-1'].parts['Part-1'].MakeSketchTransform( 74 | sketchPlane=mdb.models['Model-1'].parts['Part-1'].faces[4], 75 | sketchPlaneSide=SIDE1, 76 | sketchUpEdge=mdb.models['Model-1'].parts['Part-1'].edges[11], 77 | sketchOrientation=RIGHT, origin=(5.0, 5.0, 0.5))) 78 | mdb.models['Model-1'].parts['Part-1'].projectReferencesOntoSketch(filter= 79 | COPLANAR_EDGES, sketch=mdb.models['Model-1'].sketches['__profile__']) 80 | mdb.models['Model-1'].sketches['__profile__'].Line(point1=(0.0, 0.0), point2=( 81 | 5.0, 5.0)) 82 | mdb.models['Model-1'].sketches['__profile__'].CoincidentConstraint( 83 | addUndoState=False, entity1= 84 | mdb.models['Model-1'].sketches['__profile__'].vertices[7], entity2= 85 | mdb.models['Model-1'].sketches['__profile__'].geometry[7]) 86 | del mdb.models['Model-1'].sketches['__profile__'] 87 | mdb.models['Model-1'].ConstrainedSketch(gridSpacing=0.7, name='__profile__', 88 | sheetSize=28.28, transform= 89 | mdb.models['Model-1'].parts['Part-1'].MakeSketchTransform( 90 | sketchPlane=mdb.models['Model-1'].parts['Part-1'].faces[4], 91 | sketchPlaneSide=SIDE1, 92 | sketchUpEdge=mdb.models['Model-1'].parts['Part-1'].edges[11], 93 | sketchOrientation=RIGHT, origin=(5.0, 5.0, 0.5))) 94 | mdb.models['Model-1'].parts['Part-1'].projectReferencesOntoSketch(filter= 95 | COPLANAR_EDGES, sketch=mdb.models['Model-1'].sketches['__profile__']) 96 | mdb.models['Model-1'].sketches['__profile__'].Line(point1=(0.0, 0.0), point2=( 97 | 5.0, 5.0)) 98 | mdb.models['Model-1'].sketches['__profile__'].CoincidentConstraint( 99 | addUndoState=False, entity1= 100 | mdb.models['Model-1'].sketches['__profile__'].vertices[7], entity2= 101 | mdb.models['Model-1'].sketches['__profile__'].geometry[7]) 102 | mdb.models['Model-1'].parts['Part-1'].PartitionFaceBySketch(faces= 103 | mdb.models['Model-1'].parts['Part-1'].faces.getSequenceFromMask(('[#10 ]', 104 | ), ), sketch=mdb.models['Model-1'].sketches['__profile__'], sketchUpEdge= 105 | mdb.models['Model-1'].parts['Part-1'].edges[11]) 106 | del mdb.models['Model-1'].sketches['__profile__'] 107 | mdb.models['Model-1'].ConstrainedSketch(gridSpacing=0.7, name='__profile__', 108 | sheetSize=28.28, transform= 109 | mdb.models['Model-1'].parts['Part-1'].MakeSketchTransform( 110 | sketchPlane=mdb.models['Model-1'].parts['Part-1'].faces[0], 111 | sketchPlaneSide=SIDE1, 112 | sketchUpEdge=mdb.models['Model-1'].parts['Part-1'].edges[4], 113 | sketchOrientation=RIGHT, origin=(3.333333, 4.333333, 0.5))) 114 | mdb.models['Model-1'].parts['Part-1'].projectReferencesOntoSketch(filter= 115 | COPLANAR_EDGES, sketch=mdb.models['Model-1'].sketches['__profile__']) 116 | mdb.models['Model-1'].sketches['__profile__'].Line(point1=(-0.666667, 1.666667) 117 | , point2=(4.333333, -3.333333)) 118 | mdb.models['Model-1'].sketches['__profile__'].CoincidentConstraint( 119 | addUndoState=False, entity1= 120 | mdb.models['Model-1'].sketches['__profile__'].vertices[7], entity2= 121 | mdb.models['Model-1'].sketches['__profile__'].geometry[2]) 122 | mdb.models['Model-1'].parts['Part-1'].PartitionFaceBySketch(faces= 123 | mdb.models['Model-1'].parts['Part-1'].faces.getSequenceFromMask(('[#1 ]', 124 | ), ), sketch=mdb.models['Model-1'].sketches['__profile__'], sketchUpEdge= 125 | mdb.models['Model-1'].parts['Part-1'].edges[4]) 126 | del mdb.models['Model-1'].sketches['__profile__'] 127 | mdb.models['Model-1'].parts['Part-1'].PartitionCellByExtrudeEdge(cells= 128 | mdb.models['Model-1'].parts['Part-1'].cells.getSequenceFromMask(('[#1 ]', 129 | ), ), edges=(mdb.models['Model-1'].parts['Part-1'].edges[1], ), line= 130 | mdb.models['Model-1'].parts['Part-1'].edges[10], sense=REVERSE) 131 | mdb.models['Model-1'].parts['Part-1'].PartitionCellByExtrudeEdge(cells= 132 | mdb.models['Model-1'].parts['Part-1'].cells.getSequenceFromMask(('[#1 ]', 133 | ), ), edges=(mdb.models['Model-1'].parts['Part-1'].edges[11], ), line= 134 | mdb.models['Model-1'].parts['Part-1'].edges[17], sense=REVERSE) 135 | mdb.models['Model-1'].parts['Part-1'].PartitionCellByExtrudeEdge(cells= 136 | mdb.models['Model-1'].parts['Part-1'].cells.getSequenceFromMask(('[#2 ]', 137 | ), ), edges=(mdb.models['Model-1'].parts['Part-1'].edges[11], ), line= 138 | mdb.models['Model-1'].parts['Part-1'].edges[1], sense=FORWARD) 139 | mdb.models['Model-1'].parts['Part-1'].Set(cells= 140 | mdb.models['Model-1'].parts['Part-1'].cells.getSequenceFromMask(('[#1 ]', 141 | ), ), name='GRAIN1') 142 | mdb.models['Model-1'].parts['Part-1'].SectionAssignment(offset=0.0, 143 | offsetField='', offsetType=MIDDLE_SURFACE, region= 144 | mdb.models['Model-1'].parts['Part-1'].sets['GRAIN1'], sectionName='GRAIN1', 145 | thicknessAssignment=FROM_SECTION) 146 | mdb.models['Model-1'].parts['Part-1'].Set(cells= 147 | mdb.models['Model-1'].parts['Part-1'].cells.getSequenceFromMask(('[#4 ]', 148 | ), ), name='GRAIN2') 149 | mdb.models['Model-1'].parts['Part-1'].SectionAssignment(offset=0.0, 150 | offsetField='', offsetType=MIDDLE_SURFACE, region= 151 | mdb.models['Model-1'].parts['Part-1'].sets['GRAIN2'], sectionName='GRAIN2', 152 | thicknessAssignment=FROM_SECTION) 153 | mdb.models['Model-1'].parts['Part-1'].Set(cells= 154 | mdb.models['Model-1'].parts['Part-1'].cells.getSequenceFromMask(('[#2 ]', 155 | ), ), name='GRAIN3') 156 | mdb.models['Model-1'].parts['Part-1'].SectionAssignment(offset=0.0, 157 | offsetField='', offsetType=MIDDLE_SURFACE, region= 158 | mdb.models['Model-1'].parts['Part-1'].sets['GRAIN3'], sectionName='GRAIN3', 159 | thicknessAssignment=FROM_SECTION) 160 | mdb.models['Model-1'].parts['Part-1'].setElementType(elemTypes=(ElemType( 161 | elemCode=C3D8, elemLibrary=STANDARD, secondOrderAccuracy=OFF, 162 | distortionControl=DEFAULT), ElemType(elemCode=C3D6, elemLibrary=STANDARD), 163 | ElemType(elemCode=C3D4, elemLibrary=STANDARD)), regions=( 164 | mdb.models['Model-1'].parts['Part-1'].cells.getSequenceFromMask(('[#7 ]', 165 | ), ), )) 166 | mdb.models['Model-1'].parts['Part-1'].setMeshControls(algorithm=ADVANCING_FRONT 167 | , regions=mdb.models['Model-1'].parts['Part-1'].cells.getSequenceFromMask(( 168 | '[#7 ]', ), ), technique=SWEEP) 169 | mdb.models['Model-1'].parts['Part-1'].seedPart(deviationFactor=0.1, 170 | minSizeFactor=0.1, size=0.5) 171 | mdb.models['Model-1'].parts['Part-1'].generateMesh() 172 | # Save by engs1992 on 2020_06_05-16.07.50; build 2017 2016_09_27-22.54.59 126836 173 | from part import * 174 | from material import * 175 | from section import * 176 | from assembly import * 177 | from step import * 178 | from interaction import * 179 | from load import * 180 | from mesh import * 181 | from optimization import * 182 | from job import * 183 | from sketch import * 184 | from visualization import * 185 | from connectorBehavior import * 186 | mdb.models['Model-1'].rootAssembly.DatumCsysByDefault(CARTESIAN) 187 | mdb.models['Model-1'].rootAssembly.Instance(dependent=ON, name='Part-1-1', 188 | part=mdb.models['Model-1'].parts['Part-1']) 189 | mdb.models['Model-1'].StaticStep(name='Step-1', nlgeom=ON, previous='Initial') 190 | mdb.models['Model-1'].rootAssembly.Set(faces= 191 | mdb.models['Model-1'].rootAssembly.instances['Part-1-1'].faces.getSequenceFromMask( 192 | ('[#800 ]', ), ), name='Set-1') 193 | mdb.models['Model-1'].DisplacementBC(amplitude=UNSET, createStepName='Step-1', 194 | distributionType=UNIFORM, fieldName='', fixed=OFF, localCsys=None, name= 195 | 'BC-1', region=mdb.models['Model-1'].rootAssembly.sets['Set-1'], u1=0.0, 196 | u2=0.0, u3=0.0, ur1=UNSET, ur2=UNSET, ur3=UNSET) 197 | mdb.Job(atTime=None, contactPrint=OFF, description='', echoPrint=OFF, 198 | explicitPrecision=SINGLE, getMemoryFromAnalysis=True, historyPrint=OFF, 199 | memory=90, memoryUnits=PERCENTAGE, model='Model-1', modelPrint=OFF, 200 | multiprocessingMode=DEFAULT, name='Job-1', nodalOutputPrecision=SINGLE, 201 | numCpus=1, numGPUs=0, queue=None, resultsFormat=ODB, scratch='', type= 202 | ANALYSIS, userSubroutine='', waitHours=0, waitMinutes=0) 203 | # Save by engs1992 on 2020_06_05-16.09.17; build 2017 2016_09_27-22.54.59 126836 204 | -------------------------------------------------------------------------------- /UEL.for: -------------------------------------------------------------------------------- 1 | c 3D UEL for ABAQUS Code (Compatible with 8 node Brick Elements) 2 | c By: Daniel Spring 3 | c October 3rd, 2012 4 | c 5 | c References 6 | c 1) Seong Hyeok Song "Fracture of Asphalt Concrete: A Cohesive 7 | c Zone Modeling Approach Considering Viscoelastic Effects." PhD Thesis, 8 | c Department of Civil and Environmental Engineering, UIUC, 2006. 9 | c 10 | c 2) Kyoungsoo Park. "Concrete Fracture Mechanics and Size Effect Using 11 | c a Specialized Cohesive Zone Model" MS Thesis, Department of Civil and 12 | c Environmental Engineering, UIUC, 2005. 13 | c 14 | c 3) "Cohesive fracture model for functionally graded fiber reinforced 15 | c concrete" K. Park, G.H. Paulino, J. Roesler. Cement and Concrete 16 | c Research. Vol 40, No. 6, pp. 956-965, 2010. 17 | c 18 | c 4) "A growing library of three-dimensional cohesive elements for 19 | c use in ABAQUS" D. W. Spring, G. H. Paulino. Engineering Fracture 20 | c Mechanics. Volume 126, August 2014, Pages 190-216 21 | c 22 | c Bilinear force-separation law included 23 | c Nicolo Grilli 24 | c University of Oxford 25 | c 2 Maggio 2020 26 | c 27 | c ===================================================================== 28 | SUBROUTINE UEL (RHS, AMATRX, SVARS, ENERGY, NDOFEL, NRHS, NSVARS, 29 | 1 PROPS, NPROPS, COORDS, MCRD, NNODE, U, DU, V, A, JTYPE, TIME, 30 | 2 DTIME, KSTEP, KINC, JELEM, PARAMS, NDLOAD, JDLTYP, ADLMAG, 31 | 3 PREDEF, NPREDF, LFLAGS, MLVARX, DDLMAG, MDLOAD, PNEWDT, JPROPS, 32 | 4 NJPROP, PERIOD) 33 | c 34 | INCLUDE 'ABA_PARAM.INC' 35 | c 36 | DIMENSION RHS(MLVARX,*),AMATRX(NDOFEL,NDOFEL),PROPS(*), 37 | 1 SVARS(*),ENERGY(8),COORDS(MCRD,NNODE),U(NDOFEL), 38 | 2 DU(MLVARX,*),V(NDOFEL),A(NDOFEL),TIME(2),PARAMS(*), 39 | 3 JDLTYP(MDLOAD,*),ADLMAG(MDLOAD,*),DDLMAG(MDLOAD,*), 40 | 4 PREDEF(2,NPREDF,NNODE),LFLAGS(*),JPROPS(*) 41 | c 42 | DIMENSION ds1(4),ds2(4),dn(4),Trac(MCRD,NRHS), 43 | 1 Trac_Jacob(MCRD,MCRD),R(MCRD,MCRD),coord_l(MCRD,NNODE), 44 | 2 GP_coord(2),sf(4),B(MCRD,NDOFEL),co_de_m(3,4), 45 | 3 B_t(NDOFEL,MCRD), Transformation_M(NDOFEL,NDOFEL), 46 | 4 Transformation_M_T(NDOFEL,NDOFEL),temp1(MCRD,NDOFEL) 47 | c 48 | DIMENSION stiff_l(NDOFEL,NDOFEL),temp2(NDOFEL,NDOFEL), 49 | 1 stiff_g(NDOFEL,NDOFEL),residual_l(NDOFEL,NRHS), 50 | 2 residual_g(NDOFEL,NRHS),aJacob_M(2,3),delu_loc_gp(mcrd), 51 | 3 co_de(mcrd,nnode),damage(4) 52 | c 53 | DOUBLE PRECISION delta0, deltaF, stiffK, stiffG, sigma0 54 | 55 | c Normal and shear distance 56 | DOUBLE PRECISION opn, opt 57 | 58 | c 59 | c Define Inputs======================================================== 60 | c 61 | 62 | stiffK = PROPS(1) ! normal stiffness 63 | stiffG = PROPS(2) ! shear stiffness 64 | sigma0 = PROPS(3) ! max stress before failure 65 | delta0 = sigma0/stiffK ! max displacement before failure begins 66 | deltaF = PROPS(4) ! displacement for complete failure 67 | 68 | ! 4 Gauss points at the surface of the cohesive element 69 | GP_n=4d0 70 | 71 | c 72 | c Initialize Matrices and Vectors====================================== 73 | c 74 | ! shear and normal openings at the four nodes 75 | ! of the midsurface 76 | call k_vector_zero(ds1,4) ! ds1 has dimension 4 77 | call k_vector_zero(ds2,4) ! ds2 has dimension 4 78 | call k_vector_zero(dn,4) ! dn has dimension 4 79 | ! traction vector and derivatives 80 | ! with respect to opening displacement vector 81 | call k_matrix_zero(Trac,mcrd,nrhs) ! nrhs = 1 (apart from Riks analysis) 82 | call k_matrix_zero(Trac_Jacob,mcrd,mcrd) ! Trac_Jacob(3,3) 83 | ! rotation matrix that transforms global coordinates 84 | ! into local coordinates of the midsurface frame of reference 85 | call k_matrix_zero(R,mcrd,mcrd) ! R(3,3) 86 | ! coordinates of the nodes in the local frame of reference 87 | call k_matrix_zero(coord_l,mcrd,nnode) ! coord_l(3,8) 88 | call k_vector_zero(GP_coord,2) ! surface Gauss point coordinates (2D) 89 | call k_vector_zero(sf,4) ! sf(4) = shape functions calculated at gauss points 90 | ! transformation matrices contain one R matrix for each node 91 | call k_matrix_zero(Transformation_M,ndofel,ndofel) ! Transformation_M(24,24) 92 | call k_matrix_zero(Transformation_M_T,ndofel,ndofel) ! Transformation_M_T(24,24) 93 | ! B(3,24) matrix connects global nodal displacement with local separation 94 | call k_matrix_zero(B,mcrd,ndofel) 95 | call k_matrix_zero(B_t,ndofel,mcrd) 96 | call k_matrix_zero(temp1,mcrd,ndofel) ! used in B_t * Trac_Jacob * B 97 | ! stiffness matrix expressed in the local frame of reference 98 | call k_matrix_zero(stiff_l,ndofel,ndofel) ! stiff_l(24,24) 99 | call k_matrix_zero(temp2,ndofel,ndofel) ! used in T' * K * T 100 | ! stiffness matrix expressed in the global frame of reference 101 | call k_matrix_zero(stiff_g,ndofel,ndofel) ! stiff_g(24,24) 102 | ! residual in the local and global coordinates 103 | call k_matrix_zero(residual_l,ndofel,nrhs) ! residual_l(24,1) 104 | call k_matrix_zero(residual_g,ndofel,nrhs) ! residual_g(24,1) 105 | ! rows contain vectors on the midsurface 106 | call k_matrix_zero(aJacob_M,2,3) 107 | ! right hand side of the overall system of equations 108 | ! output for Abaqus 109 | call k_matrix_zero(rhs,ndofel,nrhs) ! rhs(24,1) 110 | ! amatrx(24,24) is the stiffness matrix of cohesive element 111 | ! given by B^T R^T D_{loc} R B (Eq. 4 in Spring 2014) 112 | ! D_{loc} is the local tangent stiffness of the cohesive element 113 | ! R is the rotation matrix of nodal displacement 114 | ! local coordinates are determined by connecting the midside points of the cohesive elements 115 | ! R transforms the global coordinates into the local coordinates 116 | call k_matrix_zero(amatrx,ndofel,ndofel) 117 | ! co_de(3,8) are the deformed coordinates of the nodes 118 | call k_matrix_zero(co_de,mcrd,nnode) 119 | a_Jacob=0.d0 120 | c 121 | c Do local computations================================================ 122 | c Determine deformed coordinates 123 | do i = 1,mcrd 124 | do j = 1,nnode 125 | co_de(i,j)=coords(i,j)+U(3.0*(j-1.0)+i) 126 | end do 127 | end do 128 | c 129 | c Do Calculations at Gauss Points====================================== 130 | c 131 | c Begin cycling over Gauss points 132 | c 133 | do i = 1,GP_n 134 | c 135 | call k_matrix_zero(aJacob_M,2,3) 136 | c 137 | gpt = i ! Gauss point index 138 | 139 | c Damage at Gauss point is stored into state variables 140 | damage(i) = SVARS(i) 141 | 142 | c 143 | c Define rotation matrix R that transforms global coordinates 144 | c into local coordinates referred to the midplane 145 | c 146 | call k_local_coordinates(gpt,co_de,R,coord_l,Transformation_M, 147 | & Transformation_M_T,a_Jacob,aJacob_M,coords,u,ndofel,nnode, 148 | & mcrd,SVARS) 149 | c 150 | c Compute shear and normal local opening displacements================== 151 | c note that the order of the nodes is 1,2,3,4 on the bottom face 152 | c corresponding to 5,6,7,8 on the top face 153 | c Coordinates are in the midsurface reference frame 154 | c 155 | do j = 1,4 156 | ds1(j)=coord_l(1,j+4)-coord_l(1,j) 157 | ds2(j)=coord_l(2,j+4)-coord_l(2,j) 158 | dn(j) =coord_l(3,j+4)-coord_l(3,j) 159 | end do 160 | c 161 | c Determine the values of the shape function at Gauss Point i 162 | c 163 | call k_shape_fun(i,sf) 164 | c 165 | call k_vector_zero(delu_loc_gp,mcrd) 166 | c 167 | c Determine shear and normal opening displacements at Gauss points 168 | c Coordinates are in the midsurface reference frame 169 | c 170 | do j = 1,4 171 | delu_loc_gp(1)=delu_loc_gp(1)+ds1(j)*sf(j) 172 | delu_loc_gp(2)=delu_loc_gp(2)+ds2(j)*sf(j) 173 | delu_loc_gp(3)=delu_loc_gp(3)+dn(j)*sf(j) 174 | end do 175 | c 176 | c Shear distance calculation 177 | c 178 | opn=delu_loc_gp(3) 179 | opt=sqrt(delu_loc_gp(1)**2.0+delu_loc_gp(2)**2.0) 180 | 181 | c 182 | c Determine Traction vector and tangent modulus matrix 183 | c Bilinear cohesive law 184 | c Trac is a pressure 185 | c Trac_Jacob is the derivative of Trac with respect to 186 | c crack opening vector in the local frame of reference 187 | c with coordinates in the midsurface reference frame 188 | 189 | call k_cohesive_law(Trac,Trac_Jacob,stiffK,stiffG,sigma0, 190 | & delta0,deltaF,delu_loc_gp,mcrd,nrhs,SVARS,damage(i)) 191 | 192 | ! assign new damage to state variables 193 | SVARS(i) = damage(i) 194 | 195 | c 196 | c Determine B matrix and its transpose 197 | c B connects the displacement at the nodes with 198 | c the crack opening along the midsurface 199 | c 200 | call k_Bmatrix(sf,B,mcrd,ndofel) 201 | c 202 | call k_matrix_transpose(B,B_t,mcrd,ndofel) 203 | c 204 | c Compute the stiffness matrix 205 | c Local Stiffness = B_t * Trac_Jacob * B 206 | c Calculated with coordinates in the midsurface reference frame 207 | c 208 | call k_matrix_multiply(Trac_Jacob,B,temp1,mcrd,mcrd, 209 | & ndofel) 210 | call k_matrix_multiply(B_t,temp1,stiff_l,ndofel, 211 | & mcrd,ndofel) 212 | c 213 | c Compute Global stiffness matrix 214 | c Global_K = T' * K * T 215 | c Transformation_M rotates coordinates of each node 216 | c from global to local system of the midsurface 217 | c 218 | call k_matrix_multiply(Transformation_M_T,stiff_l, 219 | & temp2,ndofel,ndofel,ndofel) 220 | call k_matrix_multiply(temp2,Transformation_M,stiff_g, 221 | & ndofel,ndofel,ndofel) 222 | c 223 | c Multiply Jacobian with the Global stiffness and add contribution 224 | c from each Gauss Point 225 | c Equation 4 in Spring 2014 226 | c a_Jacob is the scalar value of the area 227 | c of the midsurface (for 1 Gauss point) 228 | c 229 | call k_matrix_plus_scalar(amatrx,stiff_g,a_Jacob, 230 | & ndofel,ndofel) 231 | c 232 | c Compute the global residual vector 233 | c Local_residual = B_t * Trac 234 | c Global_residual = T' * Local_residual 235 | c Equation 5 in Spring 2014 236 | c 237 | call k_matrix_multiply(B_t,Trac,residual_l,ndofel, 238 | & mcrd,nrhs) 239 | call k_matrix_multiply(Transformation_M_T,residual_l, 240 | & residual_g,ndofel,ndofel,nrhs) 241 | c 242 | c Multiply the Global residual by the Jacobian and add the 243 | c contribution from each point 244 | c 245 | call k_matrix_plus_scalar(rhs,residual_g,a_Jacob, 246 | & ndofel,nrhs) 247 | 248 | c 249 | c End cycling over Gauss points 250 | c 251 | end do 252 | c 253 | return 254 | end 255 | c====================================================================== 256 | c=============================SUBROUTINES============================== 257 | c====================================================================== 258 | c 259 | c Determine the strain-displacement (B) matrix 260 | c B connects the displacement at the nodes with 261 | c the crack opening along the midsurface 262 | c 263 | subroutine k_Bmatrix(sf,B,mcrd,ndofel) 264 | INCLUDE 'ABA_PARAM.INC' 265 | dimension sf(4),B(mcrd,ndofel) 266 | B(1,1) = sf(1) 267 | B(1,4) = sf(2) 268 | B(1,7) = sf(3) 269 | B(1,10)= sf(4) 270 | B(1,13)= -sf(1) 271 | B(1,16)= -sf(2) 272 | B(1,19)= -sf(3) 273 | B(1,22)= -sf(4) 274 | B(2,2) = sf(1) 275 | B(2,5) = sf(2) 276 | B(2,8) = sf(3) 277 | B(2,11)= sf(4) 278 | B(2,14)= -sf(1) 279 | B(2,17)= -sf(2) 280 | B(2,20)= -sf(3) 281 | B(2,23)= -sf(4) 282 | B(3,3) = sf(1) 283 | B(3,6) = sf(2) 284 | B(3,9) = sf(3) 285 | B(3,12)= sf(4) 286 | B(3,15)= -sf(1) 287 | B(3,18)= -sf(2) 288 | B(3,21)= -sf(3) 289 | B(3,24)= -sf(4) 290 | c 291 | return 292 | end 293 | c====================================================================== 294 | c Bilinear cohesive law 295 | c 296 | 297 | subroutine k_cohesive_law(T,T_d,stiffK,stiffG,sigma0, 298 | & delta0,deltaF,delu,mcrd,nrhs,SVARS,damage) 299 | 300 | INCLUDE 'ABA_PARAM.INC' 301 | 302 | dimension T(mcrd,nrhs),T_d(mcrd,mcrd),delu(mcrd),SVARS(4) 303 | DOUBLE PRECISION stiffK, stiffG, sigma0, delta0, 304 | & deltaF, popn, popt, Tn, Tt, damage, deltaD, maxS, 305 | & Dnn, Dnt, Dtt 306 | 307 | c 308 | c Define normal and shear displacement 309 | c 310 | popn=delu(3) 311 | popt=sqrt(delu(1)**2.0+delu(2)**2.0) 312 | 313 | c Determine load case and 314 | c calculate the normal cohesive traction Tn 315 | c and shear traction Tt 316 | c 317 | if (damage .LE. 0.0) then ! undamaged 318 | 319 | ! uploading (popn < 0) 320 | ! compression (popn > 0) 321 | Tn = stiffK*popn 322 | Tt = stiffG*popt 323 | 324 | Dnn = stiffK 325 | Dtt = stiffG 326 | 327 | elseif (damage .GE. 1.0) then ! completely damaged 328 | 329 | if (popn .LT. 0.0) then ! crack open 330 | Tn = 0.0 331 | Dnn = 0.0 332 | else ! contact 333 | Tn = stiffK*popn 334 | Dnn = stiffK 335 | end if 336 | 337 | Tt = 0.0 ! shear traction always 0 when completely damaged 338 | Dtt = 0.0 339 | 340 | else ! partially damaged 341 | 342 | Tt = damage*stiffG*popt 343 | Dtt = damage*stiffG 344 | 345 | ! deltaD is the critical opening at which the 346 | ! traction behaviour changes from hardening to softening 347 | deltaD = delta0 + damage*(deltaF-delta0) 348 | 349 | ! maxS is the max stress reachable at this 350 | ! level of damage 351 | maxS = sigma0*(1.0-damage) 352 | 353 | if (popn .GE. deltaD) then ! softening 354 | Tn = maxS*(deltaF-popn)/(deltaF-deltaD) 355 | Dnn = -maxS/(deltaF-deltaD) 356 | else ! hardening 357 | Tn = maxS*popn/deltaD 358 | Dnn = maxS/deltaD 359 | end if 360 | 361 | end if 362 | 363 | ! no coupling between normal and shear force/displacement 364 | Dnt = 0.0 365 | 366 | ! assign components of the traction vector 367 | T(3,1) = Tn 368 | 369 | if (popt .EQ. 0.0) then ! avoid division by zero 370 | T(1,1) = 0.0 371 | T(2,1) = 0.0 372 | else 373 | T(1,1) = Tt*delu(1)/popt 374 | T(2,1) = Tt*delu(2)/popt 375 | end if 376 | 377 | ! assign components of the derivatives of 378 | ! the traction vector 379 | 380 | T_d(3,1) = 0.0 381 | T_d(3,2) = 0.0 382 | T_d(3,3) = Dnn 383 | 384 | T_d(1,3) = 0.0d00 385 | T_d(2,3) = 0.0d00 386 | 387 | if (popt .EQ. 0.0) then 388 | T_d(1,1) = Dtt 389 | T_d(1,2) = 0.0d00 390 | T_d(2,1) = 0.0d00 391 | T_d(2,2) = Dtt 392 | 393 | else 394 | T_d(1,1)=Dtt*(delu(1)/popt)**2.0+Tt*((delu(2)**2.0)/(popt 395 | & **3.0)) 396 | T_d(1,2)=Dtt*delu(1)*delu(2)/(popt**2.0) 397 | & -Tt*delu(1)*delu(2)/(popt**3.0) 398 | c 399 | T_d(2,1)=Dtt*delu(1)*delu(2)/(popt**2.0) 400 | & -Tt*delu(1)*delu(2)/(popt**3.0) 401 | T_d(2,2)=Dtt*(delu(2)/popt)**2.0+Tt*((delu(1)**2.0)/(popt 402 | & **3.0)) 403 | end if 404 | 405 | ! Damage accumulation 406 | if (damage .LE. 0.0) then ! undamaged 407 | if (abs(popn) .GT. delta0) then 408 | damage = damage + (abs(popn)-delta0)/(deltaF-delta0) 409 | end if 410 | else ! partially damaged 411 | 412 | ! deltaD is the critical opening at which the 413 | ! traction behaviour changes from hardening to softening 414 | deltaD = delta0 + damage*(deltaF-delta0) 415 | 416 | if (abs(popn) .GT. deltaD) then 417 | damage = damage + (abs(popn)-deltaD)/(deltaF-delta0) 418 | end if 419 | 420 | end if 421 | 422 | if (damage .GT. 1.0) then ! limit damage to 1.0 423 | damage = 1.0 424 | end if 425 | 426 | c 427 | return 428 | end 429 | 430 | c===================================================================== 431 | c determine the rotation matrix R that transforms 432 | c global coordinates into local coordinates of the midsurface 433 | c determine the Jacobian: a_Jacob, aJacob_M 434 | c 435 | subroutine k_local_coordinates(gpt,co_de,R,coord_l,Transformation_M, 436 | & Transformation_M_T,a_Jacob,aJacob_M,coords,u,ndofel,nnode, 437 | & mcrd, SVARS) 438 | INCLUDE 'ABA_PARAM.INC' 439 | dimension R(mcrd,mcrd),coord_l(mcrd,nnode),aJacob_M(2,3), 440 | & Transformation_M(ndofel,ndofel),coords(mcrd,nnode), 441 | & Transformation_M_T(ndofel,ndofel),u(ndofel), 442 | & co_de(mcrd,nnode), co_de_m(3,4),SFD(2,4),SVARS(4) 443 | 444 | ! coordinate components of the 4 midpoints of the cohesive element 445 | DOUBLE PRECISION x1, x2, x3, x4, y1, y2, y3, y4, y5, y6, z1, z2, 446 | & z3, z4 447 | c 448 | ! co_de_m(3,4) are the coordinates of the 4 midpoints of the cohesive element 449 | call k_matrix_zero(co_de_m,3,4) 450 | c 451 | do i = 1,3 452 | co_de_m(i,1)=(co_de(i,1)+co_de(i,5))*0.5 453 | co_de_m(i,2)=(co_de(i,2)+co_de(i,6))*0.5 454 | co_de_m(i,3)=(co_de(i,3)+co_de(i,7))*0.5 455 | co_de_m(i,4)=(co_de(i,4)+co_de(i,8))*0.5 456 | end do 457 | c 458 | x1=co_de_m(1,1) 459 | x2=co_de_m(1,2) 460 | x3=co_de_m(1,3) 461 | x4=co_de_m(1,4) 462 | c 463 | y1=co_de_m(2,1) 464 | y2=co_de_m(2,2) 465 | y3=co_de_m(2,3) 466 | y4=co_de_m(2,4) 467 | c 468 | z1=co_de_m(3,1) 469 | z2=co_de_m(3,2) 470 | z3=co_de_m(3,3) 471 | z4=co_de_m(3,4) 472 | c 473 | c Coordinates of the Gauss points in the reference element 474 | c with edges in plus/minus 1 475 | c 476 | if (gpt .eq. 1) then 477 | c_r=-sqrt(1.0d0/3.0d0) 478 | c_s=-sqrt(1.0d0/3.0d0) 479 | elseif (gpt .eq. 2) then 480 | c_r= sqrt(1.0d0/3.0d0) 481 | c_s=-sqrt(1.0d0/3.0d0) 482 | elseif (gpt .eq. 3) then 483 | c_r= sqrt(1.0d0/3.0d0) 484 | c_s= sqrt(1.0d0/3.0d0) 485 | elseif (gpt .eq. 4) then 486 | c_r=-sqrt(1.0d0/3.0d0) 487 | c_s= sqrt(1.0d0/3.0d0) 488 | end if 489 | c 490 | c Shape function derivatives 491 | c with respect to coordinates 1 and 2 492 | c in the reference element = dphi/dxi 493 | c 494 | SFD(1,1) =-0.25*(1-c_s) 495 | SFD(1,2) = 0.25*(1-c_s) 496 | SFD(1,3) = 0.25*(1+c_s) 497 | SFD(1,4) =-0.25*(1+c_s) 498 | SFD(2,1) =-0.25*(1-c_r) 499 | SFD(2,2) =-0.25*(1+c_r) 500 | SFD(2,3) = 0.25*(1+c_r) 501 | SFD(2,4) = 0.25*(1-c_r) 502 | c 503 | c Jacobian matrix: coordinates of the midpoints are multiplied 504 | c by the derivatives dphi/dxi, since co_de_m * phi 505 | c are the coordinates on the mid surface using shape function interpolation 506 | c then aJacob_M = dx/dxi 507 | c the two rows of aJacob_M are 3D vectors indicating the two edges 508 | c of the mid surface 509 | c 510 | do i = 1,2 511 | do j = 1,3 512 | do k =1,4 513 | aJacob_M(i,j) = aJacob_M(i,j) + SFD(i,k)*co_de_m(j,k) 514 | end do 515 | end do 516 | end do 517 | c 518 | c Vector product to find the normal of the mid surface 519 | c 520 | dum1 = aJacob_M(1,2)*aJacob_M(2,3) - aJacob_M(1,3)*aJacob_M(2,2) 521 | dum2 = aJacob_M(1,3)*aJacob_M(2,1) - aJacob_M(1,1)*aJacob_M(2,3) 522 | dum3 = aJacob_M(1,1)*aJacob_M(2,2) - aJacob_M(1,2)*aJacob_M(2,1) 523 | c 524 | a_Jacob = sqrt(dum1**2 + dum2**2 + dum3**2) 525 | c 526 | c Third row of the rotation matrix is the surface normal 527 | c see Eq. 7 in Spring 2014 528 | c 529 | R(3,1) = dum1/a_Jacob 530 | R(3,2) = dum2/a_Jacob 531 | R(3,3) = dum3/a_Jacob 532 | c 533 | c Definition of the first row of rotation matrix R 534 | c as the first vector of aJacob_M, which is the vector on the mid surface 535 | c determined by the coordinate c_s in the reference element 536 | c 537 | aLen=sqrt(aJacob_M(1,1)**2.0d00+aJacob_M(1,2)**2.0d00+aJacob_M(1,3)**2.0d00) 538 | R(1,1)=aJacob_M(1,1)/aLen 539 | R(1,2)=aJacob_M(1,2)/aLen 540 | R(1,3)=aJacob_M(1,3)/aLen 541 | c 542 | c Definition of the second row of rotation matrix R 543 | c as the vector product between midsurface normal and 544 | c first vector of aJacob_M 545 | c 546 | aLen2a=R(3,2)*R(1,3)-R(3,3)*R(1,2) 547 | aLen2b=R(3,3)*R(1,1)-R(3,1)*R(1,3) 548 | aLen2c=R(3,1)*R(1,2)-R(3,2)*R(1,1) 549 | c 550 | aLen2 = sqrt(aLen2a**2.0d00 + aLen2b**2.0d00 + aLen2c**2.0d00) 551 | c 552 | R(2,1) = aLen2a/aLen2 553 | R(2,2) = aLen2b/aLen2 554 | R(2,3) = aLen2c/aLen2 555 | c 556 | c Components of the Jacobian matrix aJacob_M 557 | c are recalculated 558 | c a_J11 = aJacob_M(1,1) 559 | c a_J12 = aJacob_M(1,3) 560 | c a_J21 = aJacob_M(2,1) 561 | c a_J22 = aJacob_M(2,3) 562 | c 563 | a_J11 = (-0.25*(1-c_s))*x1 + (0.25*(1-c_s))*x2 + (0.25*(1+c_s))*x3 + 564 | & (-0.25*(1+c_s))*x4 565 | a_J12 = (-0.25*(1-c_s))*z1 + (0.25*(1-c_s))*z2 + (0.25*(1+c_s))*z3 + 566 | & (-0.25*(1+c_s))*z4 567 | a_J21 = (-0.25*(1-c_r))*x1 + (-0.25*(1+c_r))*x2 + (0.25*(1+c_r))*x3 + 568 | & (0.25*(1-c_r))*x4 569 | a_J22 = (-0.25*(1-c_r))*z1 + (-0.25*(1+c_r))*z2 + (0.25*(1+c_r))*z3 + 570 | & (0.25*(1-c_r))*z4 571 | c 572 | b_J11 = (-0.25*(1-c_s))*x1 + (0.25*(1-c_s))*x2 + (0.25*(1+c_s))*x3 + 573 | & (-0.25*(1+c_s))*x4 574 | b_J12 = (-0.25*(1-c_s))*y1 + (0.25*(1-c_s))*y2 + (0.25*(1+c_s))*y3 + 575 | & (-0.25*(1+c_s))*y4 576 | b_J21 = (-0.25*(1-c_r))*x1 + (-0.25*(1+c_r))*x2 + (0.25*(1+c_r))*x3 + 577 | & (0.25*(1-c_r))*x4 578 | b_J22 = (-0.25*(1-c_r))*y1 + (-0.25*(1+c_r))*y2 + (0.25*(1+c_r))*y3 + 579 | & (0.25*(1-c_r))*y4 580 | c 581 | c_J11 = (-0.25*(1-c_s))*y1 + (0.25*(1-c_s))*y2 + (0.25*(1+c_s))*y3 + 582 | & (-0.25*(1+c_s))*y4 583 | c_J12 = (-0.25*(1-c_s))*z1 + (0.25*(1-c_s))*z2 + (0.25*(1+c_s))*z3 + 584 | & (-0.25*(1+c_s))*z4 585 | c_J21 = (-0.25*(1-c_r))*y1 + (-0.25*(1+c_r))*y2 + (0.25*(1+c_r))*y3 + 586 | & (0.25*(1-c_r))*y4 587 | c_J22 = (-0.25*(1-c_r))*z1 + (-0.25*(1+c_r))*z2 + (0.25*(1+c_r))*z3 + 588 | & (0.25*(1-c_r))*z4 589 | c 590 | c The following are the components of the normal vector 591 | c to the mid surface, calculated using vector product of 592 | c aJacob_M(1,1:3) and aJacob_M(2,1:3) 593 | c 594 | a_Jacob1 = (a_J11*a_J22 - a_J12*a_J21) 595 | a_Jacob2 = (b_J11*b_J22 - b_J12*b_J21) 596 | a_Jacob3 = (c_J11*c_J22 - c_J12*c_J21) 597 | c 598 | c a_Jacob is the scalar value of the area 599 | c of the midsurface (for 1 Gauss point) 600 | c note that this area change from Gauss point 601 | c to Gauss point 602 | c 603 | a_Jacob = sqrt(a_Jacob1**2.0d00 + a_Jacob2**2.0d00 + a_Jacob3**2.0d00) 604 | c 605 | c====================================================================== 606 | num=nnode 607 | c 608 | c Transformation_M(24,24) contains 8 times the rotation matrix 609 | c on the diagonal 3x3 regions of the matrix 610 | c It can rotate the coordinates of each node independently 611 | c 612 | do i = 1,num 613 | dum=3.0*(i-1.0) 614 | Transformation_M(dum+1,dum+1)=R(1,1) 615 | Transformation_M(dum+1,dum+2)=R(1,2) 616 | Transformation_M(dum+1,dum+3)=R(1,3) 617 | Transformation_M(dum+2,dum+1)=R(2,1) 618 | Transformation_M(dum+2,dum+2)=R(2,2) 619 | Transformation_M(dum+2,dum+3)=R(2,3) 620 | Transformation_M(dum+3,dum+1)=R(3,1) 621 | Transformation_M(dum+3,dum+2)=R(3,2) 622 | Transformation_M(dum+3,dum+3)=R(3,3) 623 | end do 624 | c 625 | call k_matrix_transpose(Transformation_M,Transformation_M_T, 626 | $ ndofel,ndofel) 627 | c 628 | c coord_l(3,8) are the coordinates of the cohesive element nodes 629 | c expressed in the local coordinate system 630 | c which is the coordinate system of the midsurface 631 | c 632 | do i = 1, nnode 633 | coord_l(1,i)=(R(1,1)*co_de(1,i)+R(1,2)*co_de(2,i) 634 | & +R(1,3)*co_de(3,i)) 635 | coord_l(2,i)=(R(2,1)*co_de(1,i)+R(2,2)*co_de(2,i) 636 | & +R(2,3)*co_de(3,i)) 637 | coord_l(3,i)=(R(3,1)*co_de(1,i)+R(3,2)*co_de(2,i) 638 | & +R(3,3)*co_de(3,i)) 639 | end do 640 | c 641 | return 642 | end 643 | c====================================================================== 644 | c Calculate shape function at gauss point i 645 | c of the midsurface element 646 | c 647 | subroutine k_shape_fun(i,sf) 648 | INCLUDE 'ABA_PARAM.INC' 649 | dimension sf(4), GP_coord(2) 650 | c 651 | if (i .eq. 1) then 652 | GP_coord(1)=-sqrt(1.0d0/3.0d0) 653 | GP_coord(2)=-sqrt(1.0d0/3.0d0) 654 | elseif (i .eq. 2) then 655 | GP_coord(1)= sqrt(1.0d0/3.0d0) 656 | GP_coord(2)=-sqrt(1.0d0/3.0d0) 657 | elseif (i .eq. 3) then 658 | GP_coord(1)= sqrt(1.0d0/3.0d0) 659 | GP_coord(2)= sqrt(1.0d0/3.0d0) 660 | elseif (i .eq. 4) then 661 | GP_coord(1)=-sqrt(1.0d0/3.0d0) 662 | GP_coord(2)= sqrt(1.0d0/3.0d0) 663 | end if 664 | c 665 | sf(1)=(1-GP_coord(1))*(1-GP_coord(2))*0.25 666 | sf(2)=(1+GP_coord(1))*(1-GP_coord(2))*0.25 667 | sf(3)=(1+GP_coord(1))*(1+GP_coord(2))*0.25 668 | sf(4)=(1-GP_coord(1))*(1+GP_coord(2))*0.25 669 | c 670 | return 671 | end 672 | c====================================================================== 673 | c Multiply matrices A and B to give C 674 | c 675 | subroutine k_matrix_multiply(A,B,C,l,n,m) 676 | INCLUDE 'ABA_PARAM.INC' 677 | dimension A(l,n),B(n,m),C(l,m) 678 | c 679 | call k_matrix_zero(C,l,m) 680 | c 681 | do i = 1, l 682 | do j = 1, m 683 | do k = 1, n 684 | C(i,j)=C(i,j)+A(i,k)*B(k,j) 685 | end do 686 | end do 687 | end do 688 | c 689 | return 690 | end 691 | c====================================================================== 692 | c Sum matrices with prefactor 693 | c 694 | subroutine k_matrix_plus_scalar(A,B,c,n,m) 695 | INCLUDE 'ABA_PARAM.INC' 696 | dimension A(n,m),B(n,m) 697 | c 698 | do i = 1, n 699 | do j = 1, m 700 | A(i,j)=A(i,j)+c*B(i,j) 701 | end do 702 | end do 703 | c 704 | return 705 | end 706 | c====================================================================== 707 | c Transpose matrix 708 | subroutine k_matrix_transpose(A,B,n,m) 709 | INCLUDE 'ABA_PARAM.INC' 710 | dimension A(n,m),B(m,n) 711 | c 712 | do i = 1,n 713 | do j = 1,m 714 | B(j,i)=A(i,j) 715 | end do 716 | end do 717 | c 718 | return 719 | end 720 | c====================================================================== 721 | c Initialize all the components of a matrix to zero 722 | subroutine k_matrix_zero(A,n,m) 723 | INCLUDE 'ABA_PARAM.INC' 724 | dimension A(n,m) 725 | c 726 | do i = 1, n 727 | do j = 1, m 728 | A(i,j)=0.d0 729 | end do 730 | end do 731 | c 732 | return 733 | end 734 | c====================================================================== 735 | c Initialize all the components of a vector to zero 736 | subroutine k_vector_zero(A,n) 737 | INCLUDE 'ABA_PARAM.INC' 738 | dimension A(n) 739 | c 740 | do i = 1, n 741 | A(i)=0.d0 742 | end do 743 | c 744 | return 745 | end 746 | c====================================================================== 747 | c Calculate Macaulay brackets 748 | subroutine k_Mac(pM,a,b) 749 | INCLUDE 'ABA_PARAM.INC' 750 | c 751 | if ((a-b) .GE. 0.0) then 752 | pM=a-b 753 | elseif ((a-b) .LT. 0.0) then 754 | pM=0.d0 755 | end if 756 | c 757 | return 758 | end 759 | c====================================================================== 760 | c=================================END================================== 761 | c====================================================================== 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | -------------------------------------------------------------------------------- /UMAT.for: -------------------------------------------------------------------------------- 1 | SUBROUTINE UMAT(STRESS,STATEV,DDSDDE,SSE,SPD,SCD, 2 | 1 RPL,DDSDDT,DRPLDE,DRPLDT, 3 | 2 STRAN,DSTRAN,TIME,DTIME,TEMP,DTEMP,PREDEF,DPRED,CMNAME, 4 | 3 NDI,NSHR,NTENS,NSTATV,PROPS,NPROPS,COORDS,DROT,PNEWDT, 5 | 4 CELENT,DFGRD0,DFGRD1,NOEL,NPT,LAYER,KSPT,JSTEP,KINC) 6 | C 7 | INCLUDE 'ABA_PARAM.INC' 8 | C 9 | CHARACTER*80 CMNAME 10 | DIMENSION STRESS(NTENS),STATEV(NSTATV), 11 | 1 DDSDDE(NTENS,NTENS),DDSDDT(NTENS),DRPLDE(NTENS), 12 | 2 STRAN(NTENS),DSTRAN(NTENS),TIME(2),PREDEF(1),DPRED(1), 13 | 3 PROPS(NPROPS),COORDS(3),DROT(3,3),DFGRD0(3,3),DFGRD1(3,3), 14 | 4 JSTEP(4) 15 | C ELASTIC USER SUBROUTINE 16 | PARAMETER (ONE=1.0D0, TWO=2.0D0) 17 | E=PROPS(1) 18 | ANU=PROPS(2) 19 | ALAMBDA=E/(ONE+ANU)/(ONE-TWO*ANU) 20 | BLAMBDA=(ONE-ANU) 21 | CLAMBDA=(ONE-TWO*ANU) 22 | DO I=1,NTENS 23 | DO J=1,NTENS 24 | DDSDDE(I,J)=0.0D0 25 | ENDDO 26 | ENDDO 27 | DDSDDE(1,1)=(ALAMBDA*BLAMBDA) 28 | DDSDDE(2,2)=(ALAMBDA*BLAMBDA) 29 | DDSDDE(3,3)=(ALAMBDA*BLAMBDA) 30 | DDSDDE(4,4)=(ALAMBDA*CLAMBDA) 31 | DDSDDE(5,5)=(ALAMBDA*CLAMBDA) 32 | DDSDDE(6,6)=(ALAMBDA*CLAMBDA) 33 | DDSDDE(1,2)=(ALAMBDA*ANU) 34 | DDSDDE(1,3)=(ALAMBDA*ANU) 35 | DDSDDE(2,3)=(ALAMBDA*ANU) 36 | DDSDDE(2,1)=(ALAMBDA*ANU) 37 | DDSDDE(3,1)=(ALAMBDA*ANU) 38 | DDSDDE(3,2)=(ALAMBDA*ANU) 39 | DO I=1,NTENS 40 | DO J=1,NTENS 41 | STRESS(I)=STRESS(I)+DDSDDE(I,J)*DSTRAN(J) 42 | ENDDO 43 | ENDDO 44 | RETURN 45 | END 46 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # this file tells python 2 | # that all classes in this folder can be imported 3 | -------------------------------------------------------------------------------- /abaqusparser.py: -------------------------------------------------------------------------------- 1 | # Nicolo Grilli 2 | # University of Oxford 3 | # AWE project 2020 4 | # 23 Giugno 2020 5 | 6 | # class representing abaqus input file 7 | # and functions for parsing 8 | 9 | class AbaqusParser: 10 | 11 | # prefissofile is the prefix of abaqus .inp file 12 | def __init__(self,prefissofile): 13 | self.prefissofile = prefissofile 14 | self.filename = prefissofile + '.inp' 15 | 16 | # read element sets from file 17 | # only generate keyword is accepted 18 | def ReadElsets(self): 19 | fid = open(self.filename,'r') 20 | elsetsfilename = self.prefissofile + '-elsets.inp' 21 | fout = open(elsetsfilename,'w') 22 | 23 | flagprintnextline = False # next line should be printed or not 24 | 25 | for line in fid: 26 | if (flagprintnextline): 27 | fout.write(line) 28 | flagprintnextline = False 29 | if (line[0:6] == '*Elset'): # this is an elset 30 | posgrain = line.find('GRAIN') # -1 if this is not a grain 31 | posgenerate = line.find('generate') # generate is only format accepted 32 | if (posgrain >= 0): # this is a grain 33 | if (posgenerate >= 0): 34 | fout.write(line) 35 | flagprintnextline = True # print next line containing the elements start/end 36 | else: 37 | print('generate keyword is not present' + '\n') 38 | print('generate keyword is the only accepted format' + '\n') 39 | exit() 40 | 41 | fid.close() 42 | fout.close() 43 | 44 | return 1 45 | 46 | # read nodes from file 47 | def ReadNodes(self): 48 | fid = open(self.filename,'r') 49 | nodesfilename = self.prefissofile + '-node.inp' 50 | fout = open(nodesfilename,'w') 51 | 52 | flagfoundnodes = False # block with nodes has been found 53 | 54 | for line in fid: 55 | if (flagfoundnodes): # this line may contain nodes 56 | if (line[0] == '*'): # reached the end of nodes 57 | flagfoundnodes = False 58 | break # needed because you may find also *Node Output 59 | else: 60 | fout.write(line) 61 | if (line[0:5] == '*Node'): # these are the nodes 62 | flagfoundnodes = True 63 | 64 | fid.close() 65 | fout.close() 66 | 67 | return 1 68 | 69 | # read bulk elements from file 70 | def ReadBulkElems(self): 71 | fid = open(self.filename,'r') 72 | elemsfilename = self.prefissofile + '-bulk-elems.inp' 73 | fout = open(elemsfilename,'w') 74 | 75 | flagfoundelems = False # block with elements has been found 76 | 77 | for line in fid: 78 | if (flagfoundelems): # this line may contain elements 79 | if (line[0] == '*'): # reached the end of elements 80 | flagfoundelems = False 81 | break # needed because you may find also *Element Output 82 | else: 83 | fout.write(line) 84 | if (line[0:8] == '*Element'): # these are the elements 85 | flagfoundelems = True 86 | 87 | fid.close() 88 | fout.close() 89 | 90 | return 1 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /cohelement.py: -------------------------------------------------------------------------------- 1 | # Nicolo Grilli 2 | # University of Oxford 3 | # AWE project 2020 4 | # 5 giugno 2020 5 | 6 | # class representing a COH3D8 element 7 | # at the boundary between grains 8 | 9 | import numpy as np 10 | from interfelement import InterfElement 11 | 12 | class CohElement: 13 | 14 | def __init__(self,elem1,elem2): 15 | # a cohesive elements is at the interface 16 | # of two interface elements 17 | self.elem1 = elem1 18 | self.elem2 = elem2 19 | # nodes 20 | self.nodes = np.zeros(shape=(8)) 21 | # index must follow the indices of bulk elements 22 | self.index = 0 23 | 24 | # assign nodes based on the input interface elements 25 | def AssignNodes(self): 26 | # follow the order of the first interface element 27 | # so first interface element face will be counterclockwise 28 | # and second interface element face will be clockwise 29 | temp1order = np.int_(self.elem1.cohnodeorder) 30 | temp1corr = self.elem1.corrnodes 31 | tempnodes = np.zeros(shape=(8)) 32 | for n in range(0,4): 33 | tempnodes[n] = int(self.elem1.nodes[temp1order[n]-1]) 34 | for search in range(0,4): 35 | if (temp1corr[search][0] == temp1order[n]): 36 | tempindex2 = int(temp1corr[search][1]) 37 | tempnodes[n+4] = int(self.elem2.nodes[tempindex2-1]) 38 | self.nodes = np.int_(tempnodes) 39 | return 1 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /element.py: -------------------------------------------------------------------------------- 1 | # Nicolo Grilli 2 | # University of Oxford 3 | # AWE project 2020 4 | # 4 giugno 2020 5 | 6 | # class representing a C3D8 element 7 | 8 | import numpy as np 9 | 10 | class Element: 11 | 12 | def __init__(self,index,nodes): 13 | self.index = int(index) 14 | self.nodes = np.int_(nodes) 15 | # which grain does this element belong? 16 | self.grain = 0 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /interfelement.py: -------------------------------------------------------------------------------- 1 | # Nicolo Grilli 2 | # University of Oxford 3 | # AWE project 2020 4 | # 5 giugno 2020 5 | 6 | # class representing a C3D8 element 7 | # with four nodes on the grain boundary 8 | 9 | import numpy as np 10 | from element import Element 11 | 12 | class InterfElement(Element): 13 | 14 | def __init__(self,index,nodes): 15 | super().__init__(index,nodes) 16 | # which grain does this element belong? 17 | self.grain = 0 18 | # index of the other element that shares 19 | # a face on the grain boundary with self 20 | self.shareface = 0 21 | # 6 possible grain boundary faces on C3D8 elements 22 | # 1 -> 1,2,3,4 23 | # 2 -> 5,6,7,8 24 | # 3 -> 2,3,6,7 25 | # 4 -> 1,4,5,8 26 | # 5 -> 1,2,5,6 27 | # 6 -> 3,4,7,8 28 | self.facetype = 0 29 | # store correspondence of nodes 30 | # this is a list of [n1,n2] in which n1 is the 31 | # index of the node in this elements and n2 is the index 32 | # of the corresponding node in the other elements sharing the face 33 | # indices go from 1 to 8 34 | self.corrnodes = [] 35 | # order of the nodes in the cohesive 36 | # element on this face 37 | self.cohnodeorder = np.zeros(shape=(4)) 38 | 39 | # define order of the nodes on the cohesive element 40 | # self.facetype must be assigned 41 | def OrderCohNodes(self): 42 | if (self.facetype > 0): 43 | if (self.facetype == 1): 44 | self.cohnodeorder = np.array([4,3,2,1]) 45 | if (self.facetype == 2): 46 | self.cohnodeorder = np.array([5,6,7,8]) 47 | if (self.facetype == 3): 48 | self.cohnodeorder = np.array([2,3,7,6]) 49 | if (self.facetype == 4): 50 | self.cohnodeorder = np.array([1,5,8,4]) 51 | if (self.facetype == 5): 52 | self.cohnodeorder = np.array([1,2,6,5]) 53 | if (self.facetype == 6): 54 | self.cohnodeorder = np.array([3,4,8,7]) 55 | else: 56 | print('error: facetype not assigned') 57 | return 1 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /interfnode.py: -------------------------------------------------------------------------------- 1 | # Nicolo Grilli 2 | # University of Oxford 3 | # AWE project 2020 4 | # 4 giugno 2020 5 | 6 | # class representing a node 7 | # on a grain boundary 8 | 9 | import numpy as np 10 | from node import Node 11 | 12 | class Interfnode(Node): 13 | 14 | def __init__(self,index,coords): 15 | super().__init__(index, coords) 16 | # to how many grains does this node belong? 17 | # can be 2 or 3 18 | self.multiplicity = 0 19 | # indices of the twin/triplet duplicated nodes 20 | self.twinindex = 0 21 | self.tripletindex = 0 22 | # after duplication this node will belong to a single grain 23 | self.grain = 0 24 | # after duplication connectivity will change 25 | # only elements in the same grain will be present 26 | self.newconnectivity = np.zeros(shape=(0)) 27 | 28 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # Nicolo Grilli 2 | # University of Oxford 3 | # AWE project 2020 4 | # 4 giugno 2020 5 | 6 | # main function 7 | 8 | import numpy as np 9 | from numpy import genfromtxt 10 | from abaqusparser import AbaqusParser 11 | from mesh import Mesh 12 | from interfnode import Interfnode 13 | from newmesh import NewMesh 14 | 15 | # parse abaqus input file 16 | inpfile = AbaqusParser('Job-1') 17 | inpfile.ReadElsets() 18 | inpfile.ReadNodes() 19 | inpfile.ReadBulkElems() 20 | 21 | # create mesh and parse file 22 | m = Mesh('Job-1') 23 | m.ReadElsets('Job-1') 24 | 25 | # operations on the mesh 26 | m.CreateConnectivity() 27 | m.FindInterfNodes() 28 | m.FindInterfElems() 29 | m.CreateFaces() 30 | m.CreateOrderCohNodes() 31 | 32 | # create new mesh and operations 33 | nm = NewMesh(m) 34 | nm.CreateTwinNodes() 35 | nm.MakeNewBulkElems() 36 | nm.MakeNewInterfElems() 37 | 38 | # write new mesh to file 39 | nm.WriteNewNodeFile('Job-1') 40 | nm.WriteNewElemsFile('Job-1') 41 | 42 | # cohesive elements 43 | nm.CreateCohesiveElems() 44 | nm.WriteCohElems('Job-1') 45 | 46 | # dummy elements 47 | nm.WriteDummyElems('Job-1') 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /mesh.py: -------------------------------------------------------------------------------- 1 | # Nicolo Grilli 2 | # University of Oxford 3 | # AWE project 2020 4 | # 4 giugno 2020 5 | 6 | # class representing the mesh 7 | 8 | import numpy as np 9 | from numpy import genfromtxt 10 | from element import Element 11 | from node import Node 12 | from interfnode import Interfnode 13 | from interfelement import InterfElement 14 | 15 | class Mesh: 16 | 17 | # prefissofile is the prefix of nodes 18 | # and bulk elements files 19 | def __init__(self,prefissofile): 20 | # read files with nodes and elements 21 | nodefilename = prefissofile + '-node.inp' 22 | bulkelemfilename = prefissofile + '-bulk-elems.inp' 23 | nodefile = genfromtxt(nodefilename,dtype=float,delimiter=",",skip_header=0,skip_footer=0) 24 | bulkelemfile = genfromtxt(bulkelemfilename,dtype=int,delimiter=",",skip_header=0,skip_footer=0) 25 | # define a list of nodes and elements 26 | objnodes = [] 27 | objbulkelem = [] 28 | for linea in range(0,len(nodefile)): 29 | objnodes.append(Node(nodefile[linea,0],nodefile[linea,1:4])) 30 | for linea in range(0,len(bulkelemfile)): 31 | objbulkelem.append(Element(bulkelemfile[linea,0],bulkelemfile[linea,1:9])) 32 | self.nodes = objnodes 33 | self.bulkelements = objbulkelem 34 | self.interfnodes = [] 35 | self.interfelems = [] 36 | 37 | # assign grains to elements 38 | def ReadElsets(self,prefissofile): 39 | nomefile = prefissofile + '-elsets.inp' 40 | fid = open(nomefile) 41 | indicegrain = np.zeros(shape=(0)) 42 | intervstart = np.zeros(shape=(0)) 43 | intervend = np.zeros(shape=(0)) 44 | for lineagrain in fid: 45 | if (lineagrain[0] == '*'): 46 | listakeywords = lineagrain.split(',') 47 | grainkeyword = listakeywords[1] 48 | posgrain = grainkeyword.find('GRAIN') 49 | indicegrain = np.append(indicegrain,int(grainkeyword[posgrain+5:])) 50 | else: 51 | eleminterval = lineagrain.split(',') 52 | intervstart = np.append(intervstart,int(eleminterval[0])) 53 | intervend = np.append(intervend,int(eleminterval[1])) 54 | fid.close() 55 | # check all elements and assign grain number 56 | for bulkelem in self.bulkelements: 57 | for grain in range(0,len(indicegrain)): 58 | if (bulkelem.index >= intervstart[grain] and bulkelem.index <= intervend[grain]): 59 | bulkelem.grain = int(indicegrain[grain]) 60 | return 1 61 | 62 | # assign to each node the elements 63 | # to which the node belongs 64 | def CreateConnectivity(self): 65 | for bulkelem in self.bulkelements: # cycle over bulk elements 66 | for indicenode in range(0,8): 67 | tempnode = bulkelem.nodes[indicenode] # tempnode is abaqus node index 68 | tempobjnode = self.nodes[tempnode-1] 69 | tempobjnode.connectivity = np.append(tempobjnode.connectivity,int(bulkelem.index)) 70 | return 1 71 | 72 | # search through all nodes and find the ones 73 | # at the grain boundaries 74 | def FindInterfNodes(self): 75 | objinterfnodes = [] # list of interface nodes 76 | for node in self.nodes: 77 | tempindex = node.index 78 | tempcoords = node.coords 79 | tempconnectivity = np.int_(node.connectivity) 80 | nodegrains = np.zeros(shape=(len(tempconnectivity))) # grains of the neighbouring elements 81 | count = 0 82 | for elem in tempconnectivity: # elem is abaqus element index 83 | nodegrains[count] = int(self.bulkelements[elem-1].grain) 84 | count = count + 1 85 | # check if all grains in the elements surrounding the node are the same 86 | IsBulkNode = np.all(nodegrains == nodegrains[0]) 87 | if (not IsBulkNode): # this is an interface node 88 | objinterfnodes.append(Interfnode(tempindex,tempcoords)) 89 | objinterfnodes[-1].connectivity = tempconnectivity 90 | # calculate multiplicity, 3 if it is at a triple junction 91 | objinterfnodes[-1].multiplicity = len(np.unique(nodegrains)) 92 | # this node will belong to the grain with smallest index 93 | objinterfnodes[-1].grain = int(min(nodegrains)) 94 | # assign new connectivity 95 | count = 0 96 | for elem in tempconnectivity: 97 | if (nodegrains[count] == objinterfnodes[-1].grain): 98 | objinterfnodes[-1].newconnectivity = np.append(objinterfnodes[-1].newconnectivity,int(elem)) 99 | count = count + 1 100 | self.interfnodes = objinterfnodes 101 | return 1 102 | 103 | # search through the elements and find the ones 104 | # that have four nodes on the grain boundaries 105 | # in common with another element 106 | # note that one element can have four nodes on a first GB 107 | # and four other nodes on a second GB 108 | # in this case it will be counted twice 109 | def FindInterfElems(self): 110 | objinterfelems = [] # list of interface elements 111 | indexobjinterfelems = [] # keep a list of indices of elements pairs identified to avoid repetition 112 | for node in self.interfnodes: 113 | tempconnectivity = np.int_(node.connectivity) 114 | # check all the pairs and see if two elements in different grains 115 | # share four nodes 116 | for x in tempconnectivity: 117 | xnodegrains = int(self.bulkelements[x-1].grain) 118 | for y in tempconnectivity: 119 | ynodegrains = int(self.bulkelements[y-1].grain) 120 | if (xnodegrains != ynodegrains): # excludes the same element case 121 | xnodes = self.bulkelements[x-1].nodes 122 | ynodes = self.bulkelements[y-1].nodes 123 | countcommonnodes = 0 124 | for c in range(0,8): 125 | if xnodes[c] in ynodes: 126 | countcommonnodes = countcommonnodes + 1 127 | if (countcommonnodes == 4): # this is a common face on the GB 128 | if ([x,y] not in indexobjinterfelems): # not already included 129 | if ([y,x] not in indexobjinterfelems): # not already included 130 | objinterfelems.append(InterfElement(x,xnodes)) 131 | objinterfelems[-1].shareface = y 132 | objinterfelems[-1].grain = xnodegrains 133 | objinterfelems.append(InterfElement(y,ynodes)) 134 | objinterfelems[-1].shareface = x 135 | objinterfelems[-1].grain = ynodegrains 136 | indexobjinterfelems.append([int(x),int(y)]) 137 | self.interfelems = objinterfelems 138 | return 1 139 | 140 | # create faces between interface elements 141 | def CreateFaces(self): 142 | # they are already arranged in pairs with common face 143 | for nelem in range(0,len(self.interfelems),2): 144 | tempelem1 = self.interfelems[nelem] 145 | tempelem2 = self.interfelems[nelem+1] 146 | tempnodes1 = tempelem1.nodes 147 | tempnodes2 = tempelem2.nodes 148 | # check positions of the common interface nodes in the elements 149 | posintnodes1 = np.zeros(shape=(4)) 150 | posintnodes2 = np.zeros(shape=(4)) 151 | count = 0 152 | for n in range(0,8): 153 | if tempnodes1[n] in tempnodes2: 154 | posintnodes1[count] = n+1 # index from 1 to 8 155 | dove = np.where(tempnodes2 == tempnodes1[n]) 156 | tempelem1.corrnodes.append([n+1,int(dove[0])+1]) # index from 1 to 8 157 | count = count + 1 158 | count = 0 159 | for n in range(0,8): 160 | if tempnodes2[n] in tempnodes1: 161 | posintnodes2[count] = n+1 # index from 1 to 8 162 | dove = np.where(tempnodes1 == tempnodes2[n]) 163 | tempelem2.corrnodes.append([n+1,int(dove[0])+1]) # index from 1 to 8 164 | count = count + 1 165 | # assign face type to interface element 166 | if 1 in posintnodes1: # can be type 1, 4 or 5 167 | if 2 in posintnodes1: # can be type 1 or 5 168 | if 3 in posintnodes1: # can be type 1 only 169 | self.interfelems[nelem].facetype = 1 170 | else: # can be type 5 only 171 | self.interfelems[nelem].facetype = 5 172 | else: # can be type 4 only 173 | self.interfelems[nelem].facetype = 4 174 | else: # can be type 2, 3 or 6 175 | if 2 in posintnodes1: # can be type 3 only 176 | self.interfelems[nelem].facetype = 3 177 | else: # can be type 2 or 6 178 | if 3 in posintnodes1: # can be type 6 only 179 | self.interfelems[nelem].facetype = 6 180 | else: # can be type 2 only 181 | self.interfelems[nelem].facetype = 2 182 | # do the same for the pair element 183 | if 1 in posintnodes2: # can be type 1, 4 or 5 184 | if 2 in posintnodes2: # can be type 1 or 5 185 | if 3 in posintnodes2: # can be type 1 only 186 | self.interfelems[nelem+1].facetype = 1 187 | else: # can be type 5 only 188 | self.interfelems[nelem+1].facetype = 5 189 | else: # can be type 4 only 190 | self.interfelems[nelem+1].facetype = 4 191 | else: # can be type 2, 3 or 6 192 | if 2 in posintnodes2: # can be type 3 only 193 | self.interfelems[nelem+1].facetype = 3 194 | else: # can be type 2 or 6 195 | if 3 in posintnodes2: # can be type 6 only 196 | self.interfelems[nelem+1].facetype = 6 197 | else: # can be type 2 only 198 | self.interfelems[nelem+1].facetype = 2 199 | return 1 200 | 201 | # order cohesive nodes counterclockwise 202 | def CreateOrderCohNodes(self): 203 | for n in range(0,len(self.interfelems)): 204 | self.interfelems[n].OrderCohNodes() 205 | return 1 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /newmesh.py: -------------------------------------------------------------------------------- 1 | # Nicolo Grilli 2 | # University of Oxford 3 | # AWE project 2020 4 | # 5 giugno 2020 5 | 6 | # class representing the mesh 7 | # with interface elements 8 | 9 | import numpy as np 10 | from numpy import genfromtxt 11 | from mesh import Mesh 12 | from element import Element 13 | from node import Node 14 | from interfnode import Interfnode 15 | from twinnode import TwinNode 16 | from cohelement import CohElement 17 | 18 | class NewMesh: 19 | 20 | # construct based on the old mesh 21 | def __init__(self,mesh): 22 | self.nodes = mesh.nodes 23 | self.bulkelements = mesh.bulkelements 24 | self.interfnodes = mesh.interfnodes 25 | self.interfelems = mesh.interfelems 26 | self.twinnodes = [] 27 | # initialize new elements as the old ones, then modify 28 | self.newbulkelems = mesh.bulkelements 29 | # initialize new interface elements as the old ones, then modify 30 | self.newinterfelems = mesh.interfelems 31 | # cohesive elements 32 | self.cohelems = [] 33 | 34 | # duplicate nodes and create twin/triplet nodes 35 | def CreateTwinNodes(self): 36 | objtwinnodes = [] 37 | Nnodes = len(self.nodes) # original number of nodes 38 | count = Nnodes # increasing index for the duplicated nodes 39 | for objinterfnode in self.interfnodes: 40 | if (objinterfnode.multiplicity == 2): # this is a twin 41 | count = count + 1 42 | objtwinnodes.append(TwinNode(count,objinterfnode.coords)) 43 | objtwinnodes[-1].originalnode = int(objinterfnode.index) 44 | objinterfnode.twinindex = count 45 | objtwinnodes[-1].connectivity = objinterfnode.connectivity 46 | objtwinnodes[-1].multiplicity = objinterfnode.multiplicity 47 | for elemindex in objtwinnodes[-1].connectivity: 48 | if (self.bulkelements[elemindex-1].grain != objinterfnode.grain): 49 | objtwinnodes[-1].grain = self.bulkelements[elemindex-1].grain 50 | break 51 | # assign new connectivity of twin node 52 | for elemindex in objtwinnodes[-1].connectivity: 53 | if (self.bulkelements[elemindex-1].grain == objtwinnodes[-1].grain): 54 | objtwinnodes[-1].newconnectivity = np.int_(np.append(objtwinnodes[-1].newconnectivity,elemindex)) 55 | if (objinterfnode.multiplicity == 3): # this is a triplet 56 | count = count + 1 57 | objtwinnodes.append(TwinNode(count,objinterfnode.coords)) 58 | objtwinnodes[-1].originalnode = int(objinterfnode.index) 59 | objinterfnode.twinindex = count 60 | objtwinnodes[-1].connectivity = objinterfnode.connectivity 61 | objtwinnodes[-1].multiplicity = objinterfnode.multiplicity 62 | for elemindex in objtwinnodes[-1].connectivity: 63 | if (self.bulkelements[elemindex-1].grain != objinterfnode.grain): 64 | objtwinnodes[-1].grain = self.bulkelements[elemindex-1].grain 65 | break 66 | # assign new connectivity of twin node 67 | for elemindex in objtwinnodes[-1].connectivity: 68 | if (self.bulkelements[elemindex-1].grain == objtwinnodes[-1].grain): 69 | objtwinnodes[-1].newconnectivity = np.int_(np.append(objtwinnodes[-1].newconnectivity,elemindex)) 70 | count = count + 1 71 | objtwinnodes.append(TwinNode(count,objinterfnode.coords)) 72 | objtwinnodes[-1].originalnode = int(objinterfnode.index) 73 | objinterfnode.tripletindex = count 74 | objtwinnodes[-1].connectivity = objinterfnode.connectivity 75 | objtwinnodes[-1].multiplicity = objinterfnode.multiplicity 76 | for elemindex in objtwinnodes[-1].connectivity: 77 | if (self.bulkelements[elemindex-1].grain != objinterfnode.grain): 78 | if (self.bulkelements[elemindex-1].grain != objtwinnodes[-2].grain): 79 | objtwinnodes[-1].grain = self.bulkelements[elemindex-1].grain 80 | break 81 | # assign new connectivity of triplet node 82 | for elemindex in objtwinnodes[-1].connectivity: 83 | if (self.bulkelements[elemindex-1].grain == objtwinnodes[-1].grain): 84 | objtwinnodes[-1].newconnectivity = np.int_(np.append(objtwinnodes[-1].newconnectivity,elemindex)) 85 | self.twinnodes = objtwinnodes 86 | return 1 87 | 88 | # assign duplicated nodes to elements 89 | def MakeNewBulkElems(self): 90 | for node in self.twinnodes: 91 | tempconnectivity = node.newconnectivity 92 | # only elements connected to twin nodes must be changed 93 | for elem in tempconnectivity: # elem and node belong to same grain already 94 | count = 0 95 | for tempnode in self.newbulkelems[elem-1].nodes: 96 | # if this node is an original node, then substitute with twin node 97 | if (self.newbulkelems[elem-1].nodes[count] == node.originalnode): 98 | self.newbulkelems[elem-1].nodes[count] = node.index 99 | count = count + 1 100 | return 1 101 | 102 | # assign duplicated nodes to interface elements 103 | # just copy from new bulk elements modified earlier 104 | def MakeNewInterfElems(self): 105 | for nelem in range(0,len(self.newinterfelems)): 106 | bulkindex = self.newinterfelems[nelem].index # index of the corresponding bulk element 107 | self.newinterfelems[nelem].nodes = np.int_(self.newbulkelems[bulkindex-1].nodes) 108 | return 1 109 | 110 | # build cohesive elements 111 | # self.newinterfelems have the new nodes after the application of MakeNewInterfElems 112 | # the nodes of new bulk elements must be used 113 | def CreateCohesiveElems(self): 114 | objcohel = [] 115 | # they are already arranged in pairs with common face 116 | # index of the interface elements is the 117 | # same as the index of the new bulk elements 118 | count = len(self.newbulkelems) 119 | for nelem in range(0,len(self.interfelems),2): 120 | tempelem1 = self.newinterfelems[nelem] 121 | tempelem2 = self.newinterfelems[nelem+1] 122 | objcohel.append(CohElement(tempelem1,tempelem2)) 123 | objcohel[-1].AssignNodes() 124 | count = count + 1 125 | objcohel[-1].index = count 126 | self.cohelems = objcohel 127 | return 1 128 | 129 | # write new elements file 130 | def WriteNewElemsFile(self,prefissofile): 131 | objelems = self.newbulkelems 132 | Nelems = len(self.newbulkelems) # number of elements 133 | newelems = np.zeros(shape=(Nelems,9)) 134 | for n in range(0,Nelems): 135 | newelems[n,0] = int(objelems[n].index) 136 | newelems[n,1:9] = np.int_(objelems[n].nodes) 137 | np.savetxt(prefissofile + '-bulk-elems-new.inp', newelems, fmt='%d,%d,%d,%d,%d,%d,%d,%d,%d') 138 | return 1 139 | 140 | # write new node file 141 | def WriteNewNodeFile(self,prefissofile): 142 | objnodes = self.nodes 143 | Nnodes = len(self.nodes) # original number of nodes 144 | objtwinnodes = self.twinnodes 145 | Ntwinnodes = len(self.twinnodes) 146 | newnode = np.zeros(shape=(Nnodes+Ntwinnodes,4)) 147 | for n in range(0,Nnodes): 148 | tempnode = objnodes[n] 149 | newnode[n,0] = tempnode.index 150 | newnode[n,1:4] = tempnode.coords 151 | for n in range(0,Ntwinnodes): 152 | tempnode = objtwinnodes[n] 153 | newnode[n+Nnodes,0] = tempnode.index 154 | newnode[n+Nnodes,1:4] = tempnode.coords 155 | np.savetxt(prefissofile + '-node-new.inp', newnode, fmt='%d,%f,%f,%f') 156 | 157 | # write cohesive elements 158 | def WriteCohElems(self,prefissofile): 159 | objcohelems = self.cohelems 160 | Ncohelems = len(self.cohelems) # number of cohesive elements 161 | cohelemarray = np.zeros(shape=(Ncohelems,9)) 162 | for n in range(0,Ncohelems): 163 | tempcohelem = objcohelems[n] 164 | cohelemarray[n,0] = int(tempcohelem.index) 165 | cohelemarray[n,1:9] = np.int_(tempcohelem.nodes) 166 | np.savetxt(prefissofile + '-int-elems.inp', cohelemarray, fmt='%d,%d,%d,%d,%d,%d,%d,%d,%d') 167 | return 1 168 | 169 | # write dummy elements to visualise UEL state variables 170 | def WriteDummyElems(self,prefissofile): 171 | # they are already arranged in pairs with common face 172 | # index of the dummy element runs first over the first neighbouring element 173 | # of each interface elements, then over the second neighbouring element 174 | # of each interface element 175 | Ncohelems = len(self.cohelems) 176 | count = len(self.newbulkelems) + Ncohelems 177 | count2 = 0 178 | # same number of dummy elements as interface elements 179 | Ndummyelems = 2*Ncohelems 180 | dummyelemsarray = np.zeros(shape=(Ndummyelems,9)) 181 | for nelem in range(0,Ndummyelems,2): 182 | tempelem1 = self.newinterfelems[nelem] 183 | tempelem2 = self.newinterfelems[nelem+1] 184 | dummyelemsarray[count2,1:9] = np.int_(tempelem1.nodes) 185 | dummyelemsarray[count2+Ncohelems,1:9] = np.int_(tempelem2.nodes) 186 | count = count + 1 187 | dummyelemsarray[count2,0] = count 188 | dummyelemsarray[count2+Ncohelems,0] = count+Ncohelems 189 | count2 = count2 + 1 190 | np.savetxt(prefissofile + '-dummy-elems.inp', dummyelemsarray, fmt='%d,%d,%d,%d,%d,%d,%d,%d,%d') 191 | return 1 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /node.py: -------------------------------------------------------------------------------- 1 | # Nicolo Grilli 2 | # University of Oxford 3 | # AWE project 2020 4 | # 4 giugno 2020 5 | 6 | # class representing a node of a C3D8 element 7 | 8 | import numpy as np 9 | 10 | class Node: 11 | 12 | def __init__(self,index,coords): 13 | self.index = int(index) 14 | self.coords = coords 15 | # indices of the elements that contain this node (abaqus index) 16 | self.connectivity = np.zeros(shape=(0)) 17 | -------------------------------------------------------------------------------- /twinnode.py: -------------------------------------------------------------------------------- 1 | # Nicolo Grilli 2 | # University of Oxford 3 | # AWE project 2020 4 | # 4 giugno 2020 5 | 6 | # class representing a twin (or triplet) node 7 | # index will be = number of node + index of twins 8 | # it will be the index in the new input file 9 | # triplets will be duplicated twice 10 | # and will be next to each other in the new input file 11 | 12 | import numpy as np 13 | from node import Node 14 | 15 | class TwinNode(Node): 16 | 17 | def __init__(self,index,coords): 18 | super().__init__(index, coords) 19 | # to how many grains does this node belong? 20 | # can be 2 or 3 21 | self.multiplicity = 0 22 | self.originalnode = 0 # original abaqus index of the node 23 | # this node will belong to a single grain 24 | self.grain = 0 25 | # after duplication connectivity will change 26 | # only elements in the same grain will be present 27 | self.newconnectivity = np.zeros(shape=(0)) 28 | 29 | --------------------------------------------------------------------------------