ceremonyclient/node/hypergraph/inmem/hypergraph_crdt.go

243 lines
5.8 KiB
Go
Raw Normal View History

2024-06-21 06:20:05 +00:00
package inmem
import "errors"
var ErrInvalidLocation error = errors.New("invalid location")
var ErrMissingExtrinsics error = errors.New("missing extrinsics")
var ErrIsExtrinsic error = errors.New("is extrinsic")
type HypergraphCRDT struct {
locations map[Location]struct{}
vertexAdds map[Location]*IdSet
vertexRemoves map[Location]*IdSet
hyperedgeAdds map[Location]*IdSet
hyperedgeRemoves map[Location]*IdSet
}
func NewHypergraphCRDT(locations []Location) *HypergraphCRDT {
hypergraph := &HypergraphCRDT{
locations: make(map[Location]struct{}),
vertexAdds: make(map[Location]*IdSet),
vertexRemoves: make(map[Location]*IdSet),
hyperedgeAdds: make(map[Location]*IdSet),
hyperedgeRemoves: make(map[Location]*IdSet),
}
for _, l := range locations {
hypergraph.locations[l] = struct{}{}
hypergraph.vertexAdds[l] = NewIdSet("vertex")
hypergraph.vertexRemoves[l] = NewIdSet("vertex")
hypergraph.hyperedgeAdds[l] = NewIdSet("hyperedge")
hypergraph.hyperedgeRemoves[l] = NewIdSet("hyperedge")
}
return hypergraph
}
func (hg *HypergraphCRDT) AddAtom(a Atom) error {
switch v := a.(type) {
case *Vertex:
hg.AddVertex(v)
return nil
case *Hyperedge:
return hg.AddHyperedge(v)
}
return nil
}
func (hg *HypergraphCRDT) AddVertex(v *Vertex) {
shardMap := ShardVertex(v)
for location, vertices := range shardMap {
for _, vertex := range vertices.VertexSet.atoms {
if vert, ok := vertex.(*Vertex); ok {
hg.vertexAdds[location].Add(vert)
}
}
}
}
func (hg *HypergraphCRDT) AddVertexSet(vertices *IdSet) error {
if vertices.atomType != "vertex" {
return ErrInvalidAtomType
}
shardMap := ShardAtomSet(vertices.atoms)
for location, vertices := range shardMap {
for _, vertex := range vertices.VertexSet.atoms {
if vert, ok := vertex.(*Vertex); ok {
hg.vertexAdds[location].Add(vert)
}
}
}
return nil
}
func (hg *HypergraphCRDT) AddHyperedge(h *Hyperedge) error {
if hg.LookupAtomSet(h.extrinsics) {
shardMap := ShardHyperedge(h)
for location, set := range shardMap {
for _, hyperedge := range set.HyperedgeSet.atoms {
if he, ok := hyperedge.(*Hyperedge); ok {
hg.hyperedgeAdds[location].Add(he)
}
}
for _, vertex := range set.VertexSet.atoms {
if v, ok := vertex.(*Vertex); ok {
hg.hyperedgeAdds[location].Add(v)
}
}
}
return nil
} else {
return ErrMissingExtrinsics
}
}
func (hg *HypergraphCRDT) RemoveAtom(a Atom) error {
switch v := a.(type) {
case *Vertex:
return hg.RemoveVertex(v)
case *Hyperedge:
return hg.RemoveHyperedge(v)
}
return nil
}
func (hg *HypergraphCRDT) RemoveVertex(v *Vertex) error {
if hg.LookupVertex(v) {
for l, hyperedgeAdds := range hg.hyperedgeAdds {
for _, hyperedge := range hyperedgeAdds.atoms {
he, ok := hyperedge.(*Hyperedge)
if !ok {
continue
}
if !hg.hyperedgeRemoves[l].Has(he) {
if _, ok := he.extrinsics[v.GetID()]; ok {
return ErrIsExtrinsic
}
}
}
}
hg.vertexRemoves[v.location].Add(v)
}
return nil
}
func (hg *HypergraphCRDT) RemoveHyperedge(h *Hyperedge) error {
if hg.LookupAtom(h) {
for l, hyperedgeAdds := range hg.hyperedgeAdds {
for _, hyperedge := range hyperedgeAdds.atoms {
he, ok := hyperedge.(*Hyperedge)
if !ok || hg.hyperedgeRemoves[l].Has(he) {
continue
}
if _, ok := he.extrinsics[h.GetID()]; ok {
return ErrIsExtrinsic
}
}
}
hg.hyperedgeRemoves[h.location].Add(h)
}
return nil
}
func (hg *HypergraphCRDT) LookupAtom(a Atom) bool {
if _, ok := hg.locations[a.GetLocation()]; !ok {
return false
}
switch v := a.(type) {
case *Vertex:
return hg.LookupVertex(v)
case *Hyperedge:
return hg.LookupHyperedge(v)
default:
return false
}
}
// LookupAtomSet checks all atoms in an IdSet to see if they all can be looked
// up successfully.
func (hg *HypergraphCRDT) LookupAtomSet(atomSet map[string]Atom) bool {
for _, atom := range atomSet {
if !hg.LookupAtom(atom) {
return false
}
}
return true
}
// LookupVertex checks if a vertex is added and not removed in the current
// location.
func (hg *HypergraphCRDT) LookupVertex(v *Vertex) bool {
location := v.GetLocation()
return hg.vertexAdds[location].Has(v) && !hg.vertexRemoves[location].Has(v)
}
// LookupHyperedge checks if a hyperedge and its extrinsics can be looked up.
func (hg *HypergraphCRDT) LookupHyperedge(h *Hyperedge) bool {
return hg.LookupAtomSet(h.extrinsics) &&
hg.hyperedgeAdds[h.GetLocation()].Has(h) &&
!hg.hyperedgeRemoves[h.GetLocation()].Has(h)
}
// Within checks if atom `a` is within hyperedge `h` directly or transitively.
func (hg *HypergraphCRDT) Within(a, h Atom) bool {
switch ha := h.(type) {
case *Hyperedge:
_, ok := ha.extrinsics[a.GetID()]
if ok || a.GetID() == h.GetID() {
return true
}
for _, extrinsic := range ha.extrinsics {
if he, ok := extrinsic.(*Hyperedge); ok {
for _, hyperExtrinsic := range he.extrinsics {
if hyperHe, ok := hyperExtrinsic.(*Hyperedge); ok {
if hg.LookupHyperedge(hyperHe) {
if _, ok := hyperHe.extrinsics[a.GetID()]; ok &&
hg.Within(hyperHe, h) {
return true
}
}
}
}
}
}
}
return false
}
// GetReconciledVertexSet computes the set of vertices that have been added but
// not removed for a location.
func (hg *HypergraphCRDT) GetReconciledVertexSet(l Location) *IdSet {
vertices := NewIdSet("vertex")
for _, v := range hg.vertexAdds[l].atoms {
if !hg.vertexRemoves[l].Has(v) {
vertices.Add(v)
}
}
return vertices
}
// GetReconciledHyperedgeSet computes the set of hyperedges that have been added
// but not removed for a location.
func (hg *HypergraphCRDT) GetReconciledHyperedgeSet(l Location) *IdSet {
hyperedges := NewIdSet("hyperedge")
for _, h := range hg.hyperedgeAdds[l].atoms {
if !hg.hyperedgeRemoves[l].Has(h) {
hyperedges.Add(h)
}
}
return hyperedges
}