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, 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 }