예제 #1
0
func (o *ElemU) contact_g(x []float64) float64 {
	if false {
		return x[0] - 1.025
	}

	r := make([]float64, 3)
	Y := o.contact_get_Y()
	qua4 := shp.Get("qua4", 0) //o.Sim.GoroutineId)
	qua4.InvMap(r, x, Y)
	δ := o.Cell.Shp.CellBryDist(r)
	return δ
}
예제 #2
0
파일: e_phi.go 프로젝트: PatrickSchm/gofem
// register element
func init() {

	// information allocator
	infogetters["phi"] = func(cellType string, faceConds []*FaceCond) *Info {

		// new info
		var info Info

		nverts := shp.GetNverts(cellType)
		ykeys := []string{"h"}

		info.Dofs = make([][]string, nverts)
		for m := 0; m < nverts; m++ {
			info.Dofs[m] = ykeys
		}

		info.T1vars = ykeys

		// return information
		return &info
	}

	// element allocator
	eallocators["phi"] = func(cellType string, faceConds []*FaceCond, cid int, edat *inp.ElemData, x [][]float64) Elem {

		// basic data
		var o ElemPhi
		o.Cid = cid
		o.X = x
		o.Shp = shp.Get(cellType) // cellType: e.g. "tri6", "qua8"
		o.Nu = o.Shp.Nverts

		// integration points
		o.IpsElem, _ = GetIntegrationPoints(edat.Nip, edat.Nipf, cellType)
		if o.IpsElem == nil {
			return nil // => failed
		}

		// local starred variables
		nip := len(o.IpsElem)
		o.ψs = make([]float64, nip)

		// scratchpad. computed @ each ip
		o.K = la.MatAlloc(o.Nu, o.Nu)

		// return new element
		return &o
	}
}
예제 #3
0
파일: msh.go 프로젝트: PaddySchmidt/gofem
// GetSimilar allocates a copy of this cell
//  Note: the resulting cell with share the same slices as the original one => not a full copy
func (o *Cell) GetSimilar(lbb bool) (newcell *Cell) {

	// new cell
	newcell = new(Cell)

	// input data
	newcell.Id = o.Id
	newcell.Tag = o.Tag
	newcell.Geo = o.Geo
	newcell.Type = o.Type
	newcell.Part = o.Part
	newcell.Verts = o.Verts
	newcell.FTags = o.FTags
	newcell.STags = o.STags
	newcell.JlinId = o.JlinId
	newcell.JsldId = o.JsldId

	// neighbours
	newcell.Neighs = o.Neighs

	// new cell type
	ctype := o.Shp.Type
	if lbb {
		ctype = o.Shp.BasicType
	}

	// derived
	if o.Shp.Nurbs == nil {
		newcell.Shp = shp.Get(ctype, o.GoroutineId)
		if newcell.Shp == nil {
			chk.Panic("cannot allocate \"shape\" structure for cell type = %q\n", ctype)
		}
	} else {
		chk.Panic("cannot handle similar cells with NURBS yet")
	}
	newcell.FaceBcs = o.FaceBcs
	newcell.GoroutineId = o.GoroutineId

	// specific problems data
	newcell.IsJoint = o.IsJoint
	newcell.SeepVerts = o.SeepVerts

	// NURBS
	newcell.Nrb = o.Nrb
	newcell.Span = o.Span
	return
}
예제 #4
0
func (o *ElemU) contact_dgdx(dgdx, x []float64) {
	if false {
		dgdx[0], dgdx[1] = 1.0, 0.0
	}

	r := make([]float64, 3)
	Y := o.contact_get_Y()
	qua4 := shp.Get("qua4", 0) //o.Sim.GoroutineId)
	qua4.InvMap(r, x, Y)
	dfdR := make([]float64, 2)
	o.Cell.Shp.CellBryDistDeriv(dfdR, r)
	qua4.CalcAtR(Y, r, true)
	dgdx[0], dgdx[1] = 0.0, 0.0
	for i := 0; i < 2; i++ {
		for k := 0; k < 2; k++ {
			dgdx[i] += dfdR[k] * qua4.DRdx[k][i]
		}
	}
}
예제 #5
0
파일: e_u.go 프로젝트: PatrickSchm/gofem
// register element
func init() {

	// information allocator
	infogetters["u"] = func(cellType string, faceConds []*FaceCond) *Info {

		// new info
		var info Info

		// number of nodes in element
		nverts := shp.GetNverts(cellType)

		// solution variables
		ykeys := []string{"ux", "uy"}
		if Global.Ndim == 3 {
			ykeys = []string{"ux", "uy", "uz"}
		}
		info.Dofs = make([][]string, nverts)
		for m := 0; m < nverts; m++ {
			info.Dofs[m] = ykeys
		}

		// maps
		info.Y2F = map[string]string{"ux": "fx", "uy": "fy", "uz": "fz"}

		// t1 and t2 variables
		info.T2vars = ykeys
		return &info
	}

	// element allocator
	eallocators["u"] = func(cellType string, faceConds []*FaceCond, cid int, edat *inp.ElemData, x [][]float64) Elem {

		// basic data
		var o ElemU
		o.Cid = cid
		o.X = x
		o.Shp = shp.Get(cellType)
		ndim := Global.Ndim
		o.Nu = ndim * o.Shp.Nverts

		// parse flags
		o.UseB, o.Debug, o.Thickness = GetSolidFlags(edat.Extra)

		// integration points
		o.IpsElem, o.IpsFace = GetIntegrationPoints(edat.Nip, edat.Nipf, cellType)
		if o.IpsElem == nil || o.IpsFace == nil {
			return nil
		}
		nip := len(o.IpsElem)

		// model
		var prms fun.Prms
		o.Model, prms = GetAndInitSolidModel(edat.Mat, ndim)
		if o.Model == nil {
			return nil
		}

		// model specialisations
		switch m := o.Model.(type) {
		case msolid.Small:
			o.MdlSmall = m
		case msolid.Large:
			o.MdlLarge = m
		default:
			chk.Panic("__internal_error__: 'u' element cannot determine the type of the material model")
		}

		// parameters
		for _, p := range prms {
			switch p.N {
			case "rho":
				o.Rho = p.V
			case "Cdam":
				o.Cdam = p.V
			}
		}

		// local starred variables
		o.ζs = la.MatAlloc(nip, ndim)
		o.χs = la.MatAlloc(nip, ndim)
		o.divχs = make([]float64, nip)

		// scratchpad. computed @ each ip
		nsig := 2 * ndim
		o.grav = make([]float64, ndim)
		o.us = make([]float64, ndim)
		o.fi = make([]float64, o.Nu)
		o.D = la.MatAlloc(nsig, nsig)
		o.K = la.MatAlloc(o.Nu, o.Nu)
		if o.UseB {
			o.B = la.MatAlloc(nsig, o.Nu)
		}

		// strains
		o.ε = make([]float64, nsig)
		o.Δε = make([]float64, nsig)

		// variables for debugging
		if o.Debug {
			o.fex = make([]float64, o.Shp.Nverts)
			o.fey = make([]float64, o.Shp.Nverts)
			if ndim == 3 {
				o.fez = make([]float64, o.Shp.Nverts)
			}
		}

		// surface loads (natural boundary conditions)
		for _, fc := range faceConds {
			o.NatBcs = append(o.NatBcs, &NaturalBc{fc.Cond, fc.FaceId, fc.Func, fc.Extra})
		}

		// return new element
		return &o
	}
}
예제 #6
0
파일: msh.go 프로젝트: PatrickSchm/gofem
// 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
}
예제 #7
0
파일: e_rod.go 프로젝트: PatrickSchm/gofem
// register element
func init() {

	// information allocator
	infogetters["rod"] = func(cellType string, faceConds []*FaceCond) *Info {

		// new info
		var info Info

		// number of nodes in element
		nverts := shp.GetNverts(cellType)

		// solution variables
		ykeys := []string{"ux", "uy"}
		if Global.Ndim == 3 {
			ykeys = []string{"ux", "uy", "uz"}
		}
		info.Dofs = make([][]string, nverts)
		for m := 0; m < nverts; m++ {
			info.Dofs[m] = ykeys
		}

		// maps
		info.Y2F = map[string]string{"ux": "fx", "uy": "fy", "uz": "fz"}

		// t1 and t2 variables
		info.T2vars = ykeys
		return &info
	}

	// element allocator
	eallocators["rod"] = func(cellType string, faceConds []*FaceCond, cid int, edat *inp.ElemData, x [][]float64) Elem {

		// basic data
		var o Rod
		o.Cid = cid
		o.X = x
		o.Shp = shp.Get(cellType)
		ndim := Global.Ndim
		o.Nu = ndim * o.Shp.Nverts

		var err error

		// material model name
		matname := edat.Mat
		matdata := Global.Sim.Mdb.Get(matname)
		if LogErrCond(matdata == nil, "materials database failed on getting %q material\n", matname) {
			return nil
		}
		mdlname := matdata.Model
		o.Model = msolid.GetOnedSolid(Global.Sim.Data.FnameKey, matname, mdlname, false)
		if LogErrCond(o.Model == nil, "cannot find model named %s\n", mdlname) {
			return nil
		}
		err = o.Model.Init(ndim, matdata.Prms)
		if LogErr(err, "Model.Init failed") {
			return nil
		}

		// parameters
		for _, p := range matdata.Prms {
			switch p.N {
			case "A":
				o.A = p.V
			case "rho":
				o.Rho = p.V
			}
		}

		// integration points
		var nip int
		if s_nip, found := io.Keycode(edat.Extra, "nip"); found {
			nip = io.Atoi(s_nip)
		}
		o.IpsElem, err = shp.GetIps(o.Shp.Type, nip)
		if LogErr(err, "GetIps failed") {
			return nil
		}
		nip = len(o.IpsElem)

		// scratchpad. computed @ each ip
		o.K = la.MatAlloc(o.Nu, o.Nu)
		o.M = la.MatAlloc(o.Nu, o.Nu)
		o.ue = make([]float64, o.Nu)
		o.Rus = make([]float64, o.Nu)

		// scratchpad. computed @ each ip
		o.grav = make([]float64, ndim)
		o.us = make([]float64, ndim)
		o.fi = make([]float64, o.Nu)

		// return new element
		return &o
	}
}
예제 #8
0
파일: e_p.go 프로젝트: PatrickSchm/gofem
// register element
func init() {

	// information allocator
	infogetters["p"] = func(cellType string, faceConds []*FaceCond) *Info {

		// new info
		var info Info

		// number of nodes in element
		nverts := shp.GetNverts(cellType)

		// solution variables
		ykeys := []string{"pl"}
		info.Dofs = make([][]string, nverts)
		for m := 0; m < nverts; m++ {
			info.Dofs[m] = ykeys
		}

		// maps
		info.Y2F = map[string]string{"pl": "ql"}

		// vertices on seepage faces
		lverts := GetVertsWithCond(faceConds, "seep")
		for _, m := range lverts {
			if m < nverts { // avoid adding vertices of superelement (e.g. qua8 vertices in this qua4 cell)
				info.Dofs[m] = append(info.Dofs[m], "fl")
			}
		}
		if len(lverts) > 0 {
			ykeys = append(ykeys, "fl")
			info.Y2F["fl"] = "nil"
		}

		// t1 and t2 variables
		info.T1vars = ykeys
		return &info
	}

	// element allocator
	eallocators["p"] = func(cellType string, faceConds []*FaceCond, cid int, edat *inp.ElemData, x [][]float64) Elem {

		// basic data
		var o ElemP
		o.Cid = cid
		o.X = x
		o.Shp = shp.Get(cellType)
		o.Np = o.Shp.Nverts

		// integration points
		o.IpsElem, o.IpsFace = GetIntegrationPoints(edat.Nip, edat.Nipf, cellType)
		if o.IpsElem == nil || o.IpsFace == nil {
			return nil
		}
		nip := len(o.IpsElem)

		// models
		o.Mdl = GetAndInitPorousModel(edat.Mat)
		if o.Mdl == nil {
			return nil
		}

		// local starred variables
		o.ψl = make([]float64, nip)

		// scratchpad. computed @ each ip
		ndim := Global.Ndim
		o.g = make([]float64, ndim)
		o.gpl = make([]float64, ndim)
		o.ρwl = make([]float64, ndim)
		o.tmp = make([]float64, ndim)
		o.Kpp = la.MatAlloc(o.Np, o.Np)
		o.res = new(mporous.LsVars)

		// vertices on seepage faces
		var seepverts []int
		lverts := GetVertsWithCond(faceConds, "seep")
		for _, m := range lverts {
			if m < o.Np { // avoid adding vertices of superelement (e.g. qua8 vertices in this qua4 cell)
				seepverts = append(seepverts, m)
			}
		}

		o.Nf = len(seepverts)
		o.HasSeep = o.Nf > 0
		if o.HasSeep {

			// vertices on seepage face; numbering
			o.SeepId2vid = seepverts
			o.Vid2seepId = utl.IntVals(o.Np, -1)
			o.Fmap = make([]int, o.Nf)
			for μ, m := range o.SeepId2vid {
				o.Vid2seepId[m] = μ
			}

			// flags
			o.Macaulay, o.βrmp, o.κ = GetSeepFaceFlags(edat.Extra)

			// allocate coupling matrices
			o.Kpf = la.MatAlloc(o.Np, o.Nf)
			o.Kfp = la.MatAlloc(o.Nf, o.Np)
			o.Kff = la.MatAlloc(o.Nf, o.Nf)
		}

		// set natural boundary conditions
		for idx, fc := range faceConds {
			o.NatBcs = append(o.NatBcs, &NaturalBc{fc.Cond, fc.FaceId, fc.Func, fc.Extra})

			// allocate extrapolation structures
			if fc.Cond == "ql" || fc.Cond == "seep" {
				nv := o.Shp.Nverts
				nip := len(o.IpsElem)
				o.ρl_ex = make([]float64, nv)
				o.dρldpl_ex = la.MatAlloc(nv, nv)
				o.Emat = la.MatAlloc(nv, nip)
				o.DoExtrap = true
				if LogErr(o.Shp.Extrapolator(o.Emat, o.IpsElem), "element allocation") {
					return nil
				}
			}

			// additional seepage condition structures: hydrostatic flags
			if fc.Cond == "seep" {
				if len(o.Hst) == 0 {
					o.Hst = make([]bool, len(faceConds))
				}
				if s_val, found := io.Keycode(fc.Extra, "plmax"); found {
					o.Hst[idx] = (s_val == "hst")
				}
			}
		}

		// return new element
		return &o
	}
}
예제 #9
0
파일: msh.go 프로젝트: PaddySchmidt/gofem
// ReadMsh reads a mesh for FE analyses
//  Note: returns nil on errors
func ReadMsh(dir, fn string, goroutineId int) (o *Mesh, err error) {

	// new mesh
	o = new(Mesh)

	// read file
	o.FnamePath = filepath.Join(dir, fn)
	b, err := io.ReadFile(o.FnamePath)
	if err != nil {
		return
	}

	// decode
	err = json.Unmarshal(b, &o)
	if err != nil {
		return
	}

	// check
	if len(o.Verts) < 2 {
		err = chk.Err("at least 2 vertices are required in mesh\n")
		return
	}
	if len(o.Cells) < 1 {
		err = chk.Err("at least 1 cell is required in mesh\n")
		return
	}

	// variables for NURBS
	var controlpts [][]float64
	has_nurbs := false
	if len(o.Nurbss) > 0 {
		controlpts = make([][]float64, len(o.Verts))
		has_nurbs = true
	}

	// 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 v.Id != i {
			err = chk.Err("vertices ids must coincide with order in \"verts\" list. %d != %d\n", v.Id, i)
			return
		}

		// ndim
		nd := len(v.C)
		if nd < 2 || nd > 4 {
			err = chk.Err("number of space dimensions must be 2, 3 or 4 (NURBS). %d is invalid\n", nd)
			return
		}
		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 = utl.Min(o.Xmin, v.C[0])
		o.Xmax = utl.Max(o.Xmax, v.C[0])
		o.Ymin = utl.Min(o.Ymin, v.C[1])
		o.Ymax = utl.Max(o.Ymax, v.C[1])
		if nd > 2 {
			o.Zmin = utl.Min(o.Zmin, v.C[2])
			o.Zmax = utl.Max(o.Zmax, v.C[2])
		}

		// control points to initialise NURBS
		if has_nurbs {
			controlpts[i] = make([]float64, 4)
			for j := 0; j < 4; j++ {
				controlpts[i][j] = v.C[j]
			}
		}
	}

	// allocate NURBSs
	o.PtNurbs = make([]*gm.Nurbs, len(o.Nurbss))
	o.NrbFaces = make([][]*gm.Nurbs, len(o.Nurbss))
	for i, d := range o.Nurbss {
		o.PtNurbs[i] = new(gm.Nurbs)
		o.PtNurbs[i].Init(d.Gnd, d.Ords, d.Knots)
		o.PtNurbs[i].SetControl(controlpts, d.Ctrls)
		o.NrbFaces[i] = o.PtNurbs[i].ExtractSurfaces()
	}

	// 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 c.Id != i {
			err = chk.Err("cells ids must coincide with order in \"verts\" list. %d != %d\n", c.Id, i)
			return
		}
		if c.Tag >= 0 {
			err = chk.Err("cells tags must be negative. %d is incorrect\n", c.Tag)
			return
		}

		// get shape structure
		switch c.Type {
		case "joint":
			c.IsJoint = true
		case "nurbs":
			c.Shp = shp.GetShapeNurbs(o.PtNurbs[c.Nrb], o.NrbFaces[c.Nrb], c.Span)
			if c.Shp == nil {
				err = chk.Err("cannot allocate \"shape\" structure for cell type = %q\n", c.Type)
				return
			}
		default:
			c.Shp = shp.Get(c.Type, goroutineId)
			if c.Shp == nil {
				err = chk.Err("cannot allocate \"shape\" structure for cell type = %q\n", c.Type)
				return
			}
		}
		c.GoroutineId = goroutineId

		// 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 c.Shp.FaceLocalVerts[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)
	}

	// remove duplicates
	for ftag, verts := range o.FaceTag2verts {
		o.FaceTag2verts[ftag] = utl.IntUnique(verts)
	}

	// results
	return
}