OptaPlanner学习记录4

官方例子4 评分分配网球

网球俱乐部每天有四个队进行循环赛。把这四个位置公平地分配给各队。

一个团队每天只能玩一次。
有些球队在某些日期不可用。
所有球队都应该(几乎)打相同的次数。
对抗赛:每支球队都应该与其他球队进行相同次数的比赛
如需求,每天最多有四个队伍打比赛,排七天的顺序出来。

这个在官方例子中给出了很多新用法,下边先一一说明
accumulate除了sum,count自带的方法中,还有一个AccumulateFunction接口,是 Drools 中的一个接口,用于定义在规则引擎中累积函数的行为。在规则引擎中,累积函数允许你对多个匹配的事实进行聚合操作,生成一个结果。比如在这个例子中,有两个软约束。每支球队都应该与其他球队进行相同次数的比赛。每天最多有四个队伍打比赛,排七天的顺序出来。这些都是需要全局统计的。这时候自带的方法就无法满足需求了(sum,只是计数,没法全局控制)。

该接口需要实现的方法中,accumulate比较重要,每次调用时,都会计算一个值,用于衡量队伍比赛数量。他与sum等方式不同的地方是,drl现有语法只能获得当前队伍的比赛数量,无法获得全部队伍比赛数量,也就无法平衡整体数量。

package com.example.demo.optaPlanner.solver;
import org.kie.api.runtime.rule.AccumulateFunction;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

public class LoadBalanceByCountAccumulateFunction
        implements AccumulateFunction<LoadBalanceByCountAccumulateFunction.LoadBalanceByCountData> {

    protected static class LoadBalanceByCountData implements Serializable {

        private Map<Object, Long> groupCountMap;
        // the sum of squared deviation from zero
        private long squaredSum;

    }

    @Override
    public LoadBalanceByCountData createContext() {
        return new LoadBalanceByCountData();
    }

    @Override
    public void init(LoadBalanceByCountData data) {
        data.groupCountMap = new HashMap<>();
        data.squaredSum = 0L;
    }

    @Override
    public void accumulate(LoadBalanceByCountData data, Object groupBy) {
        long count = data.groupCountMap.compute(groupBy,
                (key, value) -> (value == null) ? 1L : value + 1L);
        // squaredZeroDeviation = squaredZeroDeviation - (count - 1)² + count²
        // <=> squaredZeroDeviation = squaredZeroDeviation + (2 * count - 1)
        data.squaredSum += (2 * count - 1);
    }

    @Override
    public boolean supportsReverse() {
        return true;
    }

    @Override
    public void reverse(LoadBalanceByCountData data, Object groupBy) {
        Long count = data.groupCountMap.compute(groupBy,
                (key, value) -> (value.longValue() == 1L) ? null : value - 1L);
        data.squaredSum -= (count == null) ? 1L : (2 * count + 1);
    }

    @Override
    public Class<LoadBalanceByCountResult> getResultType() {
        return LoadBalanceByCountResult.class;
    }

    @Override
    public LoadBalanceByCountResult getResult(LoadBalanceByCountData data) {
        return new LoadBalanceByCountResult(data.squaredSum);
    }

    @Override
    public void writeExternal(ObjectOutput out) {
    }

    @Override
    public void readExternal(ObjectInput in) {
    }

    public static class LoadBalanceByCountResult implements Serializable {

        private final long squaredSum;

        public LoadBalanceByCountResult(long squaredSum) {
            this.squaredSum = squaredSum;
        }

        public long getZeroDeviationSquaredSum() {
            return squaredSum;
        }

        /**
         * @return {@link #getZeroDeviationSquaredSumRoot(double)} multiplied by {@literal 1 000}
         */
        public long getZeroDeviationSquaredSumRootMillis() {
            return getZeroDeviationSquaredSumRoot(1_000.0);
        }

        /**
         * @return {@link #getZeroDeviationSquaredSumRoot(double)} multiplied by {@literal 1 000 000}
         */
        public long getZeroDeviationSquaredSumRootMicros() {
            return getZeroDeviationSquaredSumRoot(1_000_000.0);
        }

        /**
         * @param scaleMultiplier {@code > 0}
         * @return {@code >= 0}, {@code latexmath:[f(n) = \sqrt{\sum_{i=1}^{n} (x_i - 0)^2}]} multiplied by scaleMultiplier
         */
        public long getZeroDeviationSquaredSumRoot(double scaleMultiplier) {
            return (long) (Math.sqrt((double) squaredSum) * scaleMultiplier);
        }

    }
}

除了这里有新东西以外,如果想让某个队伍固定在某天打比赛,可以使用@PlanningPin注解,为true时,在优化时,不会变动他的内容。

Day类

package com.example.demo.optaPlanner.Tennis;

import com.example.demo.optaPlanner.domain.AbstractPersistable;


public class Day extends AbstractPersistable {

    //星期几
    public Integer indexDay;

    public Day() {
    }

    public Day(long id, Integer indexDay) {
        super(id);
        this.indexDay = indexDay;
    }

    public Integer getIndexDay() {
        return indexDay;
    }

    public void setIndexDay(Integer indexDay) {
        this.indexDay = indexDay;
    }
}

Team类

package com.example.demo.optaPlanner.Tennis;

import com.example.demo.optaPlanner.domain.AbstractPersistable;


public class Team extends AbstractPersistable {

    //队伍名称
    public String teamName;

    public Team() {
    }

    public Team(long id, String teamName) {
        super(id);
        this.teamName = teamName;
    }

    public String getTeamName() {
        return teamName;
    }

    public void setTeamName(String teamName) {
        this.teamName = teamName;
    }
}

TennisAssignment类

package com.example.demo.optaPlanner.Tennis;


import com.example.demo.optaPlanner.domain.AbstractPersistable;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.entity.PlanningPin;
import org.optaplanner.core.api.domain.variable.PlanningVariable;

@PlanningEntity
public class TennisAssignment extends AbstractPersistable {

    public Day day;
    public Team team;
    public Boolean pinned;

    public TennisAssignment(){}

    public TennisAssignment(long id, Day day, Team team,Boolean pinned) {
        super(id);
        this.day = day;
        this.team = team;
        this.pinned = pinned;
    }

    public Day getDay() {
        return day;
    }

    public void setDay(Day day) {
        this.day = day;
    }

    @PlanningVariable(valueRangeProviderRefs={"teamRange"})
    public Team getTeam() {
        return team;
    }

    public void setTeam(Team team) {
        this.team = team;
    }

    //不可动
    @PlanningPin
    public Boolean getPinned() {
        return this.pinned;
    }

    public void setPinned(Boolean pinned) {
        this.pinned = pinned;
    }
}

TennisSolution类

package com.example.demo.optaPlanner.Tennis;


import com.example.demo.optaPlanner.domain.AbstractPersistable;
import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;

import java.util.List;


@PlanningSolution
public class TennisSolution extends AbstractPersistable {

    public List<Day> dayList;
    public List<Team> teamList;
    public List<TennisAssignment> tennisAssignmentList;
    public List<UnTennisAssignment> unTennisAssignmentList;
    public HardSoftScore score;

    @PlanningScore
    public HardSoftScore getScore() {
        return score;
    }

    public void setScore(HardSoftScore score) {
        this.score = score;
    }

    public TennisSolution(){}

    public TennisSolution(long id, List<Day> dayList, List<Team> teamList) {
        super(id);
        this.dayList = dayList;
        this.teamList = teamList;
    }

    public TennisSolution(long id,
                          List<Day> dayList,
                          List<Team> teamList,
                          List<TennisAssignment> tennisAssignmentList,
                          List<UnTennisAssignment> unTennisAssignmentList) {
        super(id);
        this.dayList = dayList;
        this.teamList = teamList;
        this.tennisAssignmentList = tennisAssignmentList;
        this.unTennisAssignmentList = unTennisAssignmentList;
    }

    public List<Day> getDayList() {
        return dayList;
    }

    public void setDayList(List<Day> dayList) {
        this.dayList = dayList;
    }

    @ProblemFactCollectionProperty
    @ValueRangeProvider(id = "teamRange")
    public List<Team> getTeamList() {
        return teamList;
    }

    @ProblemFactCollectionProperty
    @ValueRangeProvider(id = "unTennisAssignmentRange")
    public List<UnTennisAssignment> getUnTennisAssignmentList() {
        return unTennisAssignmentList;
    }

    public void setUnTennisAssignmentList(List<UnTennisAssignment> unTennisAssignmentList) {
        this.unTennisAssignmentList = unTennisAssignmentList;
    }

    public void setTeamList(List<Team> teamList) {
        this.teamList = teamList;
    }

    @PlanningEntityCollectionProperty
    @ValueRangeProvider(id = "tennisAssignmentRange")
    public List<TennisAssignment> getTennisAssignmentList() {
        return tennisAssignmentList;
    }

    public void setTennisAssignmentList(List<TennisAssignment> tennisAssignmentList) {
        this.tennisAssignmentList = tennisAssignmentList;
    }
}

UnTennisAssignment

package com.example.demo.optaPlanner.Tennis;


import com.example.demo.optaPlanner.domain.AbstractPersistable;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.apache.commons.lang3.tuple.Pair;
/**
 * @author wcr
 * @create 2023-11-16 17:43
 */
public class UnTennisAssignment extends AbstractPersistable {

    //哪些队在哪一天不分配比赛
    public Day day;
    public Team team;

    public UnTennisAssignment(){}

    public UnTennisAssignment(long id, Day day, Team team) {
        super(id);
        this.day = day;
        this.team = team;
    }

    public Day getDay() {
        return day;
    }

    public void setDay(Day day) {
        this.day = day;
    }

    public Team getTeam() {
        return team;
    }

    public void setTeam(Team team) {
        this.team = team;
    }
}

drl

package com.example.demo.solver;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScoreHolder;
import com.example.demo.optaPlanner.Tennis.Day;
import com.example.demo.optaPlanner.Tennis.Team
import com.example.demo.optaPlanner.Tennis.TennisAssignment
import com.example.demo.optaPlanner.Tennis.TennisSolution
import com.example.demo.optaPlanner.Tennis.UnTennisAssignment;
import accumulate com.example.demo.optaPlanner.solver.LoadBalanceByCountAccumulateFunction loadBalanceByCount;
import org.apache.commons.lang3.tuple.Pair;
global HardSoftScoreHolder scoreHolder;

//一支球队每天只能打一场比赛。
rule "oneDaybyoneTeam"
when
    TennisAssignment($id:getId(),getDay() != null,getTeam() != null,$day:getDay().getId(),$team:getTeam().getId())
    TennisAssignment($id != getId(),getDay() != null,getTeam() != null,$day == getDay().getId(),$team == getTeam().getId())
then
    scoreHolder.addHardConstraintMatch(kcontext, -1);
end

//某些队伍在某些天不能比赛
rule "UnTennisAssignment"
when
    UnTennisAssignment($day:getDay(),$team:getTeam())
    TennisAssignment($day == getDay(),$team == getTeam())
then
    scoreHolder.addHardConstraintMatch(kcontext, -1);
end

//所有球队尽可能打相同的场数
rule "fairAssignmentCountPerTeam"
    when
        accumulate(
            TennisAssignment(team != null, $t : team);
            $total : loadBalanceByCount($t)
        )
    then
        scoreHolder.addSoftConstraintMatch(kcontext,- (int) $total.getZeroDeviationSquaredSumRootMillis());
end

//球队之间尽可能打相同场数
rule "evenlyConfrontationCount"
    when
        accumulate(
            TennisAssignment(team != null, $t1 : team, $d : day)
            and TennisAssignment(team != null, $t1.getId() < team.getId(), $t2 : team, day == $d);
            $total : loadBalanceByCount(Pair.of($t1, $t2))
        )
    then
        System.out.println($total);
        scoreHolder.addSoftConstraintMatch(kcontext, - (int) $total.getZeroDeviationSquaredSumRootMillis());
end
<?xml version="1.0" encoding="UTF-8"?>
<solver>
    <!-- Domain model configuration -->
    <entityClass>com.example.demo.optaPlanner.Tennis.TennisAssignment</entityClass>
    <solutionClass>com.example.demo.optaPlanner.Tennis.TennisSolution</solutionClass>

    <!-- Score configuration -->
    <scoreDirectorFactory>
        <scoreDrl>rules5.drl</scoreDrl>
        <initializingScoreTrend>ONLY_DOWN</initializingScoreTrend>
    </scoreDirectorFactory>

    <!-- Optimization algorithms configuration -->
    <termination>
        <secondsSpentLimit>10</secondsSpentLimit>
    </termination>

    <constructionHeuristic>
        <constructionHeuristicType>FIRST_FIT</constructionHeuristicType>
    </constructionHeuristic>

    <localSearch>
        <acceptor>
            <lateAcceptanceSize>500</lateAcceptanceSize>
            <!--<entityTabuSize>7</entityTabuSize>-->
        </acceptor>
        <forager>
            <!--<acceptedCountLimit>1000</acceptedCountLimit>-->
            <acceptedCountLimit>1</acceptedCountLimit>
        </forager>
    </localSearch>
</solver>
package com.example.demo.optaPlanner;


import com.example.demo.optaPlanner.Tennis.*;
import com.example.demo.optaPlanner.nqSolver.nqAssignment;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class AppTennis {
    public static void main(String[] args) {
        startPlan();
    }

    private static void startPlan() {

        InputStream ins = AppTennis.class.getResourceAsStream("/taskassignmentConfiguration4.xml");

        SolverFactory<TennisSolution> solverFactory = SolverFactory.createFromXmlInputStream(ins);
        Solver<TennisSolution> solver = solverFactory.buildSolver();


        TennisSolution tennisSolution = build();


        TennisSolution assigned1 = solver.solve(tennisSolution);//启动引擎

        System.out.println("1111111111111111111");
        List<TennisAssignment> tennisAssignmentList = assigned1.getTennisAssignmentList();
        Map<Object, List<TennisAssignment>> collect = tennisAssignmentList.stream().collect(Collectors.groupingBy(t -> {
            return t.getDay().getIndexDay();
        }));
        for (Map.Entry<Object, List<TennisAssignment>> entry : collect.entrySet()) {
            List<Team> team = entry.getValue().stream().map(TennisAssignment::getTeam).collect(Collectors.toList());
            String TeamName = team.stream()
                    .map(Team::getTeamName)
                    .collect(Collectors.joining("->"));
            System.out.println("day:" + entry.getKey() + "  " + TeamName + "\n");
        }


        System.out.println("exit!");
    }

    public static TennisSolution build() {
        //7天  16个队伍
        List<Day> dayList = new ArrayList<>();
        for (int i = 1; i <= 7; i++) {
            dayList.add(new Day((long) i, i));
        }

        List<Team> teamList = new ArrayList<>();
        for (int i = 1; i <= 16; i++) {
            teamList.add(new Team((long) i, "team" + i));
        }

        List<TennisAssignment> tennisAssignmentList = new ArrayList<>();
        long id = 1L;
        for (int i = 0; i < 7; i++) {
            for (int j = 1; j <= 4; j++) {
                tennisAssignmentList.add(new TennisAssignment(id++, dayList.get(i), i==1 && j==2 ? teamList.get(2) : null, i==1 && j==2 ? true : false));
            }
        }


        List<UnTennisAssignment> unTennisAssignmentList = new ArrayList<>();
//        unTennisAssignmentList.add(new UnTennisAssignment(1L,dayList.get(0),teamList.get(0)));

        TennisSolution tennisSolution = new TennisSolution(1L, dayList, teamList, tennisAssignmentList, unTennisAssignmentList);
        return tennisSolution;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值