// ReadMsh reads a mesh for FE analyses // Note: returns nil on errors func ReadMsh(dir, fn string) *Mesh { // new mesh var o Mesh // read file o.FnamePath = filepath.Join(dir, fn) b, err := io.ReadFile(o.FnamePath) if LogErr(err, "msh: cannot open mesh file "+o.FnamePath) { return nil } // decode if LogErr(json.Unmarshal(b, &o), "msh: cannot unmarshal mesh file "+fn+"\n") { return nil } // check if LogErrCond(len(o.Verts) < 2, "msh: mesh must have at least 2 vertices and 1 cell") { return nil } if LogErrCond(len(o.Cells) < 1, "msh: mesh must have at least 2 vertices and 1 cell") { return nil } // vertex related derived data o.Ndim = 2 o.Xmin = o.Verts[0].C[0] o.Ymin = o.Verts[0].C[1] if len(o.Verts[0].C) > 2 { o.Zmin = o.Verts[0].C[2] } o.Xmax = o.Xmin o.Ymax = o.Ymin o.Zmax = o.Zmin o.VertTag2verts = make(map[int][]*Vert) for i, v := range o.Verts { // check vertex id if LogErrCond(v.Id != i, "msh: vertices must be sequentially numbered. %d != %d\n", v.Id, i) { return nil } // ndim nd := len(v.C) if LogErrCond(nd < 2 || nd > 4, "msh: ndim must be 2 or 3\n") { return nil } if nd == 3 { if math.Abs(v.C[2]) > Ztol { o.Ndim = 3 } } // tags if v.Tag < 0 { verts := o.VertTag2verts[v.Tag] o.VertTag2verts[v.Tag] = append(verts, v) } // limits o.Xmin = min(o.Xmin, v.C[0]) o.Xmax = max(o.Xmax, v.C[0]) o.Ymin = min(o.Ymin, v.C[1]) o.Ymax = max(o.Ymax, v.C[1]) if nd > 2 { o.Zmin = min(o.Zmin, v.C[2]) o.Zmax = max(o.Zmax, v.C[2]) } } // derived data o.CellTag2cells = make(map[int][]*Cell) o.FaceTag2cells = make(map[int][]CellFaceId) o.FaceTag2verts = make(map[int][]int) o.SeamTag2cells = make(map[int][]CellSeamId) o.Ctype2cells = make(map[string][]*Cell) o.Part2cells = make(map[int][]*Cell) for i, c := range o.Cells { // check id and tag if LogErrCond(c.Id != i, "msh: cells must be sequentially numbered. %d != %d\n", c.Id, i) { return nil } if LogErrCond(c.Tag >= 0, "msh: cell tags must be negative\n") { return nil } // face tags cells := o.CellTag2cells[c.Tag] o.CellTag2cells[c.Tag] = append(cells, c) for i, ftag := range c.FTags { if ftag < 0 { pairs := o.FaceTag2cells[ftag] o.FaceTag2cells[ftag] = append(pairs, CellFaceId{c, i}) for _, l := range shp.GetFaceLocalVerts(c.Type, i) { utl.IntIntsMapAppend(&o.FaceTag2verts, ftag, o.Verts[c.Verts[l]].Id) } } } // seam tags if o.Ndim == 3 { for i, stag := range c.STags { if stag < 0 { pairs := o.SeamTag2cells[stag] o.SeamTag2cells[stag] = append(pairs, CellSeamId{c, i}) } } } // cell type => cells cells = o.Ctype2cells[c.Type] o.Ctype2cells[c.Type] = append(cells, c) // partition => cells cells = o.Part2cells[c.Part] o.Part2cells[c.Part] = append(cells, c) // get shape structure switch c.Type { case "joint": c.IsJoint = true default: c.Shp = shp.Get(c.Type) if LogErrCond(c.Shp == nil, "msh: cannot find shape type == %q\n", c.Type) { return nil } } } // remove duplicates for ftag, verts := range o.FaceTag2verts { o.FaceTag2verts[ftag] = utl.IntUnique(verts) } // log log.Printf("msh: fn=%s nverts=%d ncells=%d ncelltags=%d nfacetags=%d nseamtags=%d nverttags=%d ncelltypes=%d npart=%d\n", fn, len(o.Verts), len(o.Cells), len(o.CellTag2cells), len(o.FaceTag2cells), len(o.SeamTag2cells), len(o.VertTag2verts), len(o.Ctype2cells), len(o.Part2cells)) return &o }
// SetStage set nodes, equation numbers and auxiliary data for given stage func (o *Domain) SetStage(idxstg int, stg *inp.Stage, distr bool) (setstageisok bool) { // backup state if idxstg > 0 { o.create_stage_copy() if !o.fix_inact_flags(stg.Activate, false) { return } if !o.fix_inact_flags(stg.Deactivate, true) { } } // auxiliary maps for setting boundary conditions o.FaceConds = make(map[int][]*FaceCond) // cid => conditions // nodes (active) and elements (active AND in this processor) o.Nodes = make([]*Node, 0) o.Elems = make([]Elem, 0) o.MyCids = make([]int, 0) // auxiliary maps for dofs and equation types o.F2Y = make(map[string]string) o.YandC = GetIsEssenKeyMap() o.Dof2Tnum = make(map[string]int) // auxiliary maps for nodes and elements o.Vid2node = make([]*Node, len(o.Msh.Verts)) o.Cid2elem = make([]Elem, len(o.Msh.Cells)) o.Cid2active = make([]bool, len(o.Msh.Cells)) // subsets of elements o.ElemConnect = make([]ElemConnector, 0) o.ElemIntvars = make([]ElemIntvars, 0) // allocate nodes and cells (active only) ------------------------------------------------------- // for each cell var eq int // current equation number => total number of equations @ end of loop o.NnzKb = 0 for _, c := range o.Msh.Cells { // get element data and information structure edat := o.Reg.Etag2data(c.Tag) if LogErrCond(edat == nil, "cannot get element's data with etag=%d", c.Tag) { return } if edat.Inact { continue } o.Cid2active[c.Id] = true // prepare maps of face conditions for faceId, faceTag := range c.FTags { if faceTag < 0 { faceBc := stg.GetFaceBc(faceTag) if faceBc != nil { lverts := shp.GetFaceLocalVerts(c.Type, faceId) gverts := o.faceLocal2globalVerts(lverts, c) for j, key := range faceBc.Keys { fcn := Global.Sim.Functions.Get(faceBc.Funcs[j]) if LogErrCond(fcn == nil, "cannot find function named %q corresponding to face tag %d (@ element %d)", faceBc.Funcs[j], faceTag, c.Id) { return } fcond := &FaceCond{faceId, lverts, gverts, key, fcn, faceBc.Extra} fconds := o.FaceConds[c.Id] o.FaceConds[c.Id] = append(fconds, fcond) } } } } // get element info (such as DOFs, etc.) info := GetElemInfo(c.Type, edat.Type, o.FaceConds[c.Id]) if info == nil { return } // for non-joint elements, add new DOFs if !c.IsJoint { chk.IntAssert(len(info.Dofs), len(c.Verts)) // store y and f information for ykey, fkey := range info.Y2F { o.F2Y[fkey] = ykey o.YandC[ykey] = true } // t1 and t2 equations for _, ykey := range info.T1vars { o.Dof2Tnum[ykey] = 1 } for _, ykey := range info.T2vars { o.Dof2Tnum[ykey] = 2 } // loop over nodes of this element var eNdof int // number of DOFs of this elmeent for j, v := range c.Verts { // new or existent node var nod *Node if o.Vid2node[v] == nil { nod = NewNode(o.Msh.Verts[v]) o.Vid2node[v] = nod o.Nodes = append(o.Nodes, nod) } else { nod = o.Vid2node[v] } // set DOFs and equation numbers for _, ukey := range info.Dofs[j] { eq = nod.AddDofAndEq(ukey, eq) eNdof += 1 } } // number of non-zeros o.NnzKb += eNdof * eNdof } // allocate element mycell := c.Part == Global.Rank // cell belongs to this processor if !distr { mycell = true // not distributed simulation => this processor has all cells } if mycell { // new element ele := NewElem(edat, c.Id, o.Msh, o.FaceConds[c.Id]) if ele == nil { return } o.Cid2elem[c.Id] = ele o.Elems = append(o.Elems, ele) o.MyCids = append(o.MyCids, ele.Id()) // give equation numbers to new element eqs := make([][]int, len(c.Verts)) for j, v := range c.Verts { for _, dof := range o.Vid2node[v].Dofs { eqs[j] = append(eqs[j], dof.Eq) } } if LogErrCond(!ele.SetEqs(eqs, nil), "cannot set element equations") { return } // subsets of elements o.add_element_to_subsets(ele) } } // connect elements (e.g. Joints) for _, e := range o.ElemConnect { nnz, ok := e.Connect(o.Cid2elem, o.Msh.Cells[e.Id()]) if LogErrCond(!ok, "Connect failed") { return } o.NnzKb += nnz } // logging log.Printf("dom: stage # %d %s\n", idxstg, stg.Desc) log.Printf("dom: nnodes=%d nelems=%d\n", len(o.Nodes), len(o.Elems)) // element conditions, essential and natural boundary conditions -------------------------------- // (re)set constraints and prescribed forces structures o.EssenBcs.Reset() o.PtNatBcs.Reset() // element conditions for _, ec := range stg.EleConds { cells, ok := o.Msh.CellTag2cells[ec.Tag] if LogErrCond(!ok, "cannot find cells with tag = %d to assign conditions", ec.Tag) { return } for _, c := range cells { e := o.Cid2elem[c.Id] if e != nil { // set conditions only for this processor's / active element for j, key := range ec.Keys { fcn := Global.Sim.Functions.Get(ec.Funcs[j]) if LogErrCond(fcn == nil, "Functions.Get failed\n") { return } e.SetEleConds(key, fcn, ec.Extra) } } } } // face boundary conditions for cidx, fcs := range o.FaceConds { c := o.Msh.Cells[cidx] for _, fc := range fcs { lverts := fc.LocalVerts gverts := o.faceLocal2globalVerts(lverts, c) var enodes []*Node for _, v := range gverts { enodes = append(enodes, o.Vid2node[v]) } if o.YandC[fc.Cond] { if !o.EssenBcs.Set(fc.Cond, enodes, fc.Func, fc.Extra) { return } } } } // vertex bounday conditions for _, nc := range stg.NodeBcs { verts, ok := o.Msh.VertTag2verts[nc.Tag] if LogErrCond(!ok, "cannot find vertices with tag = %d to assign node boundary conditions", nc.Tag) { return } for _, v := range verts { if o.Vid2node[v.Id] != nil { // set BCs only for active nodes n := o.Vid2node[v.Id] for j, key := range nc.Keys { fcn := Global.Sim.Functions.Get(nc.Funcs[j]) if LogErrCond(fcn == nil, "Functions.Get failed\n") { return } if o.YandC[key] { o.EssenBcs.Set(key, []*Node{n}, fcn, nc.Extra) } else { o.PtNatBcs.Set(o.F2Y[key], n, fcn, nc.Extra) } } } } } // resize slices -------------------------------------------------------------------------------- // t1 and t2 equations o.T1eqs = make([]int, 0) o.T2eqs = make([]int, 0) for _, nod := range o.Nodes { for _, dof := range nod.Dofs { switch o.Dof2Tnum[dof.Key] { case 1: o.T1eqs = append(o.T1eqs, dof.Eq) case 2: o.T2eqs = append(o.T2eqs, dof.Eq) default: LogErrCond(true, "t1 and t2 equations are incorrectly set") return } } } // size of arrays o.Ny = eq o.Nlam, o.NnzA = o.EssenBcs.Build(o.Ny) o.Nyb = o.Ny + o.Nlam // solution structure and linear solver o.Sol = new(Solution) o.Kb = new(la.Triplet) o.Fb = make([]float64, o.Nyb) o.Wb = make([]float64, o.Nyb) o.Kb.Init(o.Nyb, o.Nyb, o.NnzKb+2*o.NnzA) o.InitLSol = true // tell solver that lis has to be initialised before use // allocate arrays o.Sol.Y = make([]float64, o.Ny) o.Sol.ΔY = make([]float64, o.Ny) o.Sol.L = make([]float64, o.Nlam) if !Global.Sim.Data.Steady { o.Sol.Dydt = make([]float64, o.Ny) o.Sol.D2ydt2 = make([]float64, o.Ny) o.Sol.Psi = make([]float64, o.Ny) o.Sol.Zet = make([]float64, o.Ny) o.Sol.Chi = make([]float64, o.Ny) } // initialise internal variables if stg.HydroSt { if !o.SetHydroSt(stg) { return } } else if stg.GeoSt != nil { if !o.SetGeoSt(stg) { return } } else if stg.IniStress != nil { if !o.SetIniStress(stg) { return } } else { for _, e := range o.ElemIntvars { e.SetIniIvs(o.Sol, nil) } } // import results from another set of files if stg.Import != nil { sum := ReadSum(stg.Import.Dir, stg.Import.Fnk) if LogErrCond(sum == nil, "cannot import state from %s/%s.sim", stg.Import.Dir, stg.Import.Fnk) { return } if !o.In(sum, len(sum.OutTimes)-1, false) { return } if LogErrCond(o.Ny != len(o.Sol.Y), "import failed: length of primary variables vector imported is not equal to the one allocated. make sure the number of DOFs of the imported simulation matches this one. %d != %d", o.Ny, len(o.Sol.Y)) { return } if stg.Import.ResetU { for _, ele := range o.ElemIntvars { if LogErrCond(!ele.Ureset(o.Sol), "cannot run reset function of element after displacements are zeroed") { return } } for _, nod := range o.Nodes { for _, ukey := range []string{"ux", "uy", "uz"} { eq := nod.GetEq(ukey) if eq >= 0 { o.Sol.Y[eq] = 0 if len(o.Sol.Dydt) > 0 { o.Sol.Dydt[eq] = 0 o.Sol.D2ydt2[eq] = 0 } } } } } } // logging if Global.LogBcs { log.Printf("dom: essential boundary conditions:%v", o.EssenBcs.List(stg.Control.Tf)) log.Printf("dom: ptnatbcs=%v", o.PtNatBcs.List(stg.Control.Tf)) } log.Printf("dom: ny=%d nlam=%d nnzKb=%d nnzA=%d nt1eqs=%d nt2eqs=%d", o.Ny, o.Nlam, o.NnzKb, o.NnzA, len(o.T1eqs), len(o.T2eqs)) // success return true }
// NodesOnPlane finds vertices located on {x,y,z} plane and returns an iterator // that can be used to extract results on the plane. An {u,v}-coordinates system is built // on the plane. // Input: // ftag -- cells' face tag; e.g. -31 // Output: // dat -- plane data holding vertices on plane and other information; see above // Note: // 1) this function works with 3D cells only // 2) faces on cells must be either "qua4" or "qua8" types // 3) middle nodes in "qua8" are disregarded in order to buidl an {u,v} grid // 4) the resulting mesh on plane must be equally spaced; i.e. Δx and Δy are constant; // but not necessarily equal to each other func NodesOnPlane(ftag int) *PlaneData { // check ndim := Dom.Msh.Ndim if ndim != 3 { chk.Panic("this function works in 3D only") } // loop over cells on face with given face tag var dat PlaneData dat.Plane = -1 dat.Ids = make(map[int]bool) dat.Dx = make([]float64, ndim) Δx := make([]float64, ndim) first := true cells := Dom.Msh.FaceTag2cells[ftag] for _, cell := range cells { ctype := cell.C.Type for fidx, cftag := range cell.C.FTags { if cftag == ftag { // check face type ftype := shp.GetFaceType(ctype) if !(ftype == "qua4" || ftype == "qua8") { chk.Panic("can only handle qua4 or qua8 faces for now. ftype=%q", ftype) } // vertices on face flvids := shp.GetFaceLocalVerts(ctype, fidx) nv := len(flvids) if nv == 8 { nv = 4 // avoid middle nodes } for i := 0; i < nv; i++ { vid := cell.C.Verts[flvids[i]] dat.Ids[vid] = true } // compute and check increments in global coordinates for i := 1; i < nv; i++ { a := cell.C.Verts[flvids[i]] b := cell.C.Verts[flvids[i-1]] xa := Dom.Msh.Verts[a].C xb := Dom.Msh.Verts[b].C for j := 0; j < ndim; j++ { Δx[j] = max(Δx[j], math.Abs(xa[j]-xb[j])) } } if first { for j := 0; j < ndim; j++ { dat.Dx[j] = Δx[j] } first = false } else { for j := 0; j < ndim; j++ { if math.Abs(dat.Dx[j]-Δx[j]) > 1e-10 { chk.Panic("all faces must have the same Δx,Δy,Δz") } } } // find which plane vertices are located on => which plane this face is parallel to perpto := -1 // "perpendicular to" indicator switch { case Δx[0] < DIST_TOL: // plane perpendicular to x-axis perpto = 0 case Δx[1] < DIST_TOL: // plane perpendicular to y-axis perpto = 1 case Δx[2] < DIST_TOL: // plane perpendicular to z-axis perpto = 2 default: chk.Panic("planes must be perpendicular to one of the x-y-z axes") } if dat.Plane < 0 { dat.Plane = perpto } else { if perpto != dat.Plane { chk.Panic("all planes must be perperdicular to the same axis") } } } } } // uv: indices and increments dat.Iu = make([]int, 2) dat.Du = make([]float64, 2) switch dat.Plane { case 0: // plane perpendicular to x-axis dat.Iu = []int{1, 2} case 1: // plane perpendicular to y-axis dat.Iu = []int{0, 2} case 2: // plane perpendicular to z-axis dat.Iu = []int{0, 1} } for j := 0; j < 2; j++ { dat.Du[j] = dat.Dx[dat.Iu[j]] } // uv: limits dat.Umin = make([]float64, 2) dat.Umax = make([]float64, 2) first = true for vid, _ := range dat.Ids { x := Dom.Msh.Verts[vid].C if first { for j := 0; j < 2; j++ { dat.Umin[j] = x[dat.Iu[j]] dat.Umax[j] = x[dat.Iu[j]] } first = false } else { for j := 0; j < 2; j++ { dat.Umin[j] = min(dat.Umin[j], x[dat.Iu[j]]) dat.Umax[j] = max(dat.Umax[j], x[dat.Iu[j]]) } } } // uv: size of grid dat.Nu = make([]int, 2) for j := 0; j < 2; j++ { dat.Nu[j] = int((dat.Umax[j]-dat.Umin[j])/dat.Du[j]) + 1 } // uv: bins dd := DIST_TOL * 2 nb := 20 dat.Ubins.Init([]float64{dat.Umin[0] - dd, dat.Umin[1] - dd}, []float64{dat.Umax[0] + dd, dat.Umax[1] + dd}, nb) u := []float64{0, 0} for vid, _ := range dat.Ids { x := Dom.Msh.Verts[vid].C for j := 0; j < 2; j++ { u[j] = x[dat.Iu[j]] } err := dat.Ubins.Append(u, vid) if err != nil { chk.Panic("cannot append {u,v} coordinate to bins. u=%v", u) } } // allocate F dat.F = la.MatAlloc(dat.Nu[0], dat.Nu[1]) return &dat }