Files
cut-abstractions/tests/dev1/dataHandle/common/core/Individual.ts
2025-07-22 18:22:31 +08:00

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
}