Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.18

- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.42
version: v1.45.2

- name: Test
run: make test
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

Things you need to install before running:
```
Go 1.17
Go 1.18
```


Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/lothar1998/v2x-optimizer

go 1.17
go 1.18

require (
github.com/golang/mock v1.6.0
Expand Down
88 changes: 88 additions & 0 deletions pkg/optimizer/genetic/genoperator/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package genoperator

import (
"testing"

"github.com/lothar1998/v2x-optimizer/pkg/data"
"github.com/lothar1998/v2x-optimizer/pkg/optimizer/genetic/gentype"
"github.com/stretchr/testify/assert"
)

func makeBucket(bucketID int, itemIDToSize map[int]int) *gentype.Bucket {
var items []*gentype.Item

sizeSum := 0
for id, size := range itemIDToSize {
items = append(items, gentype.NewItem(id, size))
sizeSum += size
}

bucket := gentype.NewBucket(bucketID, sizeSum)

for _, item := range items {
_ = bucket.AddItem(item)
}

return bucket
}

func makeChromosome(buckets ...*gentype.Bucket) *gentype.Chromosome {
c := gentype.NewChromosome(0)
for _, bucket := range buckets {
c.Append(bucket)
}
return c
}

func makeDeepCopyOfChromosome(chromosome *gentype.Chromosome) *gentype.Chromosome {
newChromosome := gentype.NewChromosome(chromosome.Len())
for i := 0; i < newChromosome.Len(); i++ {
newChromosome.SetAt(i, chromosome.At(i).DeepCopy())
}
return newChromosome
}

func assertCompletenessOfChromosome(t *testing.T, chromosome *gentype.Chromosome, data *data.Data) {
assertNoDuplicatedBucketsInChromosome(t, chromosome)
assertAllItemsInChromosome(t, chromosome, data)
}

func assertNoDuplicatedBucketsInChromosome(t *testing.T, chromosome *gentype.Chromosome) {
bucketIDs := make(map[int]struct{})
for i := 0; i < chromosome.Len(); i++ {
bucket := chromosome.At(i)
if _, ok := bucketIDs[bucket.ID()]; !ok {
bucketIDs[bucket.ID()] = struct{}{}
} else {
assert.FailNowf(t, "duplicated bucket", "bucketID = %d", bucket.ID())
}
}
}

func assertAllItemsInChromosome(t *testing.T, chromosome *gentype.Chromosome, data *data.Data) {
itemIDs := make(map[int]struct{})

for i := 0; i < chromosome.Len(); i++ {
bucket := chromosome.At(i)

for itemID, item := range bucket.Map() {
assert.Equal(t, itemID, item.ID())
assert.Equal(t, data.R[itemID][bucket.ID()], item.Size())
if _, ok := itemIDs[itemID]; ok {
assert.FailNowf(t, "duplicated item", "itemID = %d", itemID)
}
itemIDs[itemID] = struct{}{}
}
}

for itemID := range data.R {
if _, ok := itemIDs[itemID]; !ok {
assert.FailNowf(t, "missing item in chromosome", "itemID = %d", itemID)
}
}
}

func assertOneButNotBoth(t *testing.T, value1, value2 bool) {
assert.True(t, value1 || value2)
assert.False(t, value1 && value2)
}
136 changes: 136 additions & 0 deletions pkg/optimizer/genetic/genoperator/crossover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package genoperator

import (
"errors"
"fmt"

"github.com/lothar1998/v2x-optimizer/pkg/optimizer/genetic/gentype"
)

var ErrCrossoverFailed = errors.New("unable to perform crossover")

type CrossoverOperator struct {
ItemPool *gentype.ItemPool
BucketFactory *gentype.BucketFactory
RandomGenerator RandomGenerator
}

func (c *CrossoverOperator) DoCrossover(parent1, parent2 *gentype.Chromosome) (
*gentype.Chromosome,
*gentype.Chromosome,
error,
) {
l1, r1 := getRandomChromosomeSliceBoundaries(parent1, c.RandomGenerator)
l2, r2 := getRandomChromosomeSliceBoundaries(parent2, c.RandomGenerator)

child1, err := c.doHalfCrossover(parent1, parent2.Slice(l2, r2), l1)
if err != nil {
return nil, nil, err
}
child2, err := c.doHalfCrossover(parent2, parent1.Slice(l1, r1), l2)
if err != nil {
return nil, nil, err
}

return child1, child2, nil
}

func (c *CrossoverOperator) doHalfCrossover(
parent *gentype.Chromosome,
transplant []*gentype.Bucket,
injectionIndex int,
) (*gentype.Chromosome, error) {
skippedBuckets, missingItems := getTransplantImpact(parent, transplant)

initChildLength := parent.Len() - len(skippedBuckets) + len(transplant)
child := gentype.NewChromosome(initChildLength)

var j int
var transplantInjected bool
for i := 0; i < parent.Len(); i++ {
if !transplantInjected && injectionIndex == j {
for _, bucket := range transplant {
child.SetAt(j, bucket.DeepCopy())
j++
}
transplantInjected = true
}

originalBucket := parent.At(i)

if !shouldSkipBucket(skippedBuckets, originalBucket) {
child.SetAt(j, originalBucket.DeepCopy())
j++
} else if !transplantInjected {
injectionIndex--
}
}

if !transplantInjected {
for _, bucket := range transplant {
child.SetAt(j, bucket.DeepCopy())
j++
}
}

if err := assignMissingItems(child, missingItems, c.BucketFactory, c.ItemPool); err != nil {
return nil, fmt.Errorf("%w: %s", ErrCrossoverFailed, err.Error())
}
return child, nil
}

func getTransplantImpact(
parent *gentype.Chromosome,
transplant []*gentype.Bucket,
) (map[int]struct{}, map[int]struct{}) {
transplantItems, transplantBuckets := toTransplantDetails(transplant)
skippedBuckets := make(map[int]struct{})
missingItems := make(map[int]struct{})

for i := 0; i < parent.Len(); i++ {
bucket := parent.At(i)
if _, ok := transplantBuckets[bucket.ID()]; ok {
skippedBuckets[bucket.ID()] = struct{}{}
addMissingItemsIfNotInTransplant(missingItems, bucket, transplantItems)
continue
}

for itemID := range bucket.Map() {
if _, ok := transplantItems[itemID]; ok {
skippedBuckets[bucket.ID()] = struct{}{}
addMissingItemsIfNotInTransplant(missingItems, bucket, transplantItems)
break
}
}
}

return skippedBuckets, missingItems
}

func toTransplantDetails(transplant []*gentype.Bucket) (items map[int]struct{}, buckets map[int]struct{}) {
items = make(map[int]struct{})
buckets = make(map[int]struct{})

for _, bucket := range transplant {
buckets[bucket.ID()] = struct{}{}

for itemID := range bucket.Map() {
items[itemID] = struct{}{}
}
}

return items, buckets
}

func addMissingItemsIfNotInTransplant(
missingItems map[int]struct{},
bucket *gentype.Bucket,
transplantItems map[int]struct{},
) {
for itemID := range bucket.Map() {
if _, ok := transplantItems[itemID]; ok {
continue
}
missingItems[itemID] = struct{}{}
}
}
Loading