275 lines
9.2 KiB
TypeScript
275 lines
9.2 KiB
TypeScript
import { arrayLast, arrayRemoveOnce } from '../ArrayExt'
|
|
import type { NestFiler } from '../Filer'
|
|
import { RandomIndex } from '../Random'
|
|
import type { PlaceType } from '../../vo/enums/PlaceType'
|
|
import { Container } from './Container'
|
|
import { GNestConfig } from './GNestConfig'
|
|
import { DefaultComparePointKeys } from './NestDatabase'
|
|
import type { Part } from './Part'
|
|
import type { Path } from './Path'
|
|
|
|
/**
|
|
* 个体(表示某一次优化的结果,或者还没开始优化的状态)
|
|
* 个体是由一堆零件组成的,零件可以有不同的状态。
|
|
*
|
|
* 个体单独变异
|
|
* 可以是某个零件的旋转状态发生改变
|
|
* 可以是零件的顺序发生改变
|
|
*
|
|
* 个体交配(感觉暂时不需要)
|
|
* 个体和其他个体进行基因交换
|
|
*/
|
|
export class Individual {
|
|
constructor(public Parts?: Part[], public mutationRate = 0.5, private bin?: Path, private OddmentsBins: Path[] = [], private ComparePointKeys: string[] = DefaultComparePointKeys) { }
|
|
|
|
Fitness: number // (评估健康) 大板个数-1 + 最后一块板的利用率
|
|
Containers: Container[] // 大板列表(已排
|
|
HoleContainers: Container[]// 网洞列表
|
|
OddmentsContainers: Container[]// 余料列表
|
|
/**
|
|
* 评估健康程度
|
|
*/
|
|
Evaluate(bestCount: number, greedy = false, type?: PlaceType) {
|
|
if (GNestConfig.RotateHole)
|
|
return this.EvaluateOfUseRotateHole(bestCount, greedy, type)
|
|
|
|
this.Containers = []
|
|
this.HoleContainers = []
|
|
// 初始化余料列表
|
|
this.OddmentsContainers = this.OddmentsBins.map(path => new Container(path, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)]))
|
|
this.InitHoleContainers()
|
|
|
|
// 余料优先
|
|
let parts = this.Parts.filter(p => !this.OddmentsContainers.some(odd => odd.PutPart(p)))
|
|
|
|
// 网洞优先
|
|
parts = parts.filter(p => !this.HoleContainers.some(hole => hole.PutPart(p)))
|
|
|
|
while (parts.length > 0) {
|
|
if (this.Containers.length > bestCount) {
|
|
this.Fitness = Math.ceil(bestCount) + 1
|
|
return
|
|
}
|
|
const container = new Container(this.bin, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)])
|
|
if (this.mutationRate > 0.5) // 大板优先,可以提高收敛速度
|
|
{
|
|
const maxP = parts.reduce((preP, curP) => preP.State.Contour.Area > curP.State.Contour.Area ? preP : curP)
|
|
|
|
const PutGroupF = (): boolean => {
|
|
// 基因注册
|
|
const nextPartIndex = parts.findIndex(p => p !== maxP)
|
|
if (nextPartIndex !== -1) {
|
|
const nextPart = parts[nextPartIndex]
|
|
const groups = maxP.ParseGroup(nextPart, this.bin)
|
|
for (const group of groups) {
|
|
if (container.PutPart(group)) {
|
|
parts.splice(nextPartIndex, 1)
|
|
arrayRemoveOnce(parts, maxP)
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
if (GNestConfig.UsePartGroup && PutGroupF()) { }
|
|
else if (container.PutPart(maxP, greedy)) { arrayRemoveOnce(parts, maxP) }
|
|
}
|
|
parts = parts.filter(p => !container.PutPart(p, greedy))
|
|
if (!greedy)// 如果没有贪心,排完后在贪心一下
|
|
parts = parts.filter(p => !container.PutPart(p, true))
|
|
this.Containers.push(container)
|
|
}
|
|
this.Fitness = this.Containers.length - 1 + arrayLast(this.Containers)?.UseRatio ?? 0
|
|
}
|
|
|
|
/**
|
|
* 在网洞利用时,保持纹路正确
|
|
*
|
|
* @param bestCount
|
|
* @param greedy
|
|
* @param type
|
|
*/
|
|
private EvaluateOfUseRotateHole(bestCount: number, greedy = false, type?: PlaceType) {
|
|
// 初始化余料列表
|
|
this.OddmentsContainers = this.OddmentsBins.map(path => new Container(path, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)]))
|
|
|
|
this.Containers = []
|
|
this.HoleContainers = []
|
|
let parts = this.Parts.concat()
|
|
while (parts.length > 0) {
|
|
if (this.Containers.length > bestCount)// 提前结束,已经超过最佳用板量
|
|
{
|
|
this.Fitness = Math.ceil(bestCount) + 1
|
|
return
|
|
}
|
|
|
|
const container = new Container(this.bin, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)])
|
|
const holes: Container[] = []
|
|
const PutPart = (part: Part, greedy: boolean): boolean => {
|
|
// 先放置在网洞内
|
|
const isOk
|
|
= this.OddmentsContainers.some(oc => oc.PutPart(part, greedy)) // 余料
|
|
|| holes.some((h) => {
|
|
const isOk = h.PutPart(part, greedy)
|
|
return isOk
|
|
}) // 网洞
|
|
|| container.PutPart(part, greedy) // 大板
|
|
|
|
if (isOk)
|
|
this.AppendHoles(part, holes)
|
|
|
|
return isOk
|
|
}
|
|
|
|
if (this.mutationRate > 0.5) // 大板优先,可以提高收敛速度
|
|
{
|
|
const maxP = parts.reduce((preP, curP) => preP.State.Contour.Area > curP.State.Contour.Area ? preP : curP)
|
|
|
|
const PutGroupF = (): boolean => {
|
|
// 基因注册
|
|
const nextPartIndex = parts.findIndex(p => p !== maxP)
|
|
if (nextPartIndex !== -1) {
|
|
const nextPart = parts[nextPartIndex]
|
|
const groups = maxP.ParseGroup(nextPart, this.bin)
|
|
for (const group of groups) {
|
|
if (PutPart(group, greedy)) {
|
|
parts.splice(nextPartIndex, 1)
|
|
arrayRemoveOnce(parts, maxP)
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
if (GNestConfig.UsePartGroup && PutGroupF()) { }
|
|
else if (PutPart(maxP, greedy)) { arrayRemoveOnce(parts, maxP) }
|
|
}
|
|
|
|
parts = parts.filter(p => !PutPart(p, greedy))
|
|
if (!greedy)// 如果没有贪心,排完后在贪心一下
|
|
parts = parts.filter(p => !PutPart(p, true))
|
|
|
|
if (container.PlacedParts.length)
|
|
this.Containers.push(container)
|
|
}
|
|
this.Fitness = this.Containers.length - 1 + (arrayLast(this.Containers)?.UseRatio ?? 0)
|
|
}
|
|
|
|
private AppendHoles(part: Part<any, any>, holes: Container[]) {
|
|
for (let i = 0; i < part.Holes.length; i++) {
|
|
const hole = part.Holes[i]
|
|
const container = new Container(hole.Contour)
|
|
container.ParentId = part.Id
|
|
container.ChildrenIndex = i
|
|
container.ParentM = GNestConfig.RotateHole ? hole.MinPoint : hole.OrigionMinPoint
|
|
this.HoleContainers.push(container)
|
|
holes.push(container)
|
|
}
|
|
}
|
|
|
|
private InitHoleContainers() {
|
|
if (GNestConfig.RotateHole)
|
|
return
|
|
for (const part of this.Parts) {
|
|
for (let i = 0; i < part.Holes.length; i++) {
|
|
const hole = part.Holes[i]
|
|
const container = new Container(hole.Contour)
|
|
container.ParentId = part.Id
|
|
container.ChildrenIndex = i
|
|
container.ParentM = GNestConfig.RotateHole ? hole.MinPoint : hole.OrigionMinPoint
|
|
this.HoleContainers.push(container)
|
|
}
|
|
}
|
|
}
|
|
|
|
Clone() {
|
|
const p = new Individual(this.Parts.map(p => p.Clone()), this.mutationRate, this.bin, this.OddmentsBins, this.ComparePointKeys)
|
|
p.Mutate()
|
|
return p
|
|
}
|
|
|
|
/**
|
|
* 突变
|
|
*/
|
|
Mutate() {
|
|
if (this.mutationRate > 0.5) {
|
|
for (let i = 0; i < this.Parts.length; i++) {
|
|
const rand = Math.random()
|
|
if (rand < this.mutationRate * 0.5) {
|
|
// 和下一个调换顺序
|
|
const j = i + 1
|
|
if (j < this.Parts.length)
|
|
[this.Parts[i], this.Parts[j]] = [this.Parts[j], this.Parts[i]]
|
|
}
|
|
if (rand < this.mutationRate)
|
|
this.Parts[i].Mutate()
|
|
}
|
|
}
|
|
else {
|
|
// 洗牌
|
|
const rand = Math.random()
|
|
if (rand < 0.5) {
|
|
const index = RandomIndex(this.Parts.length - 2)
|
|
const count = Math.ceil(RandomIndex(this.Parts.length - 2 - index) * this.mutationRate * 2) || 1
|
|
const parts = this.Parts.splice(index, count)
|
|
this.Parts.push(...parts)
|
|
this.Parts[index].Mutate()
|
|
}
|
|
else {
|
|
for (let i = 0; i < this.Parts.length; i++) {
|
|
const rand = Math.random()
|
|
if (rand < this.mutationRate) {
|
|
this.Parts[i].Mutate()
|
|
i += 5
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.mutationRate > 0.2)
|
|
this.mutationRate -= 0.004
|
|
return this
|
|
}
|
|
|
|
// #region -------------------------File-------------------------
|
|
|
|
// 对象从文件中读取数据,初始化自身
|
|
ReadFile(file: NestFiler) {
|
|
const ver = file.Read()// ver
|
|
|
|
this.Fitness = file.Read()
|
|
let count = file.Read() as number
|
|
this.Containers = []
|
|
for (let i = 0; i < count; i++)
|
|
this.Containers.push(new Container().ReadFile(file, this.Parts))
|
|
|
|
count = file.Read()
|
|
this.HoleContainers = []
|
|
for (let i = 0; i < count; i++)
|
|
this.HoleContainers.push(new Container().ReadFile(file, this.Parts))
|
|
|
|
count = file.Read()
|
|
this.OddmentsContainers = []
|
|
for (let i = 0; i < count; i++)
|
|
this.OddmentsContainers.push(new Container().ReadFile(file, this.Parts))
|
|
}
|
|
|
|
// 对象将自身数据写入到文件.
|
|
WriteFile(f: NestFiler) {
|
|
f.Write(1)// ver
|
|
f.Write(this.Fitness)
|
|
f.Write(this.Containers.length)
|
|
for (const c of this.Containers)
|
|
c.WriteFile(f)
|
|
|
|
f.Write(this.HoleContainers.length)
|
|
for (const c of this.HoleContainers)
|
|
c.WriteFile(f)
|
|
|
|
f.Write(this.OddmentsContainers.length)
|
|
for (const c of this.OddmentsContainers)
|
|
c.WriteFile(f)
|
|
}
|
|
// #endregion
|
|
}
|