Skip to content

Commit

Permalink
add ImitatingCellPicker
Browse files Browse the repository at this point in the history
introducing the notion of Register and refactoring the epsilon-greedy
behaviour
  • Loading branch information
nicolaspayette committed Nov 19, 2024
1 parent b30acc5 commit 66d969c
Show file tree
Hide file tree
Showing 20 changed files with 581 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.time.LocalDateTime;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

@Data
public abstract class Action implements Steppable {
Expand All @@ -44,10 +45,14 @@ public Action(
final Duration duration,
final Vessel vessel
) {
checkArgument(duration.isPositive(), "Duration must be positive");
this.start = start;
this.duration = duration;
this.vessel = vessel;
checkArgument(
duration.isPositive(),
"Duration must be positive but was %s.",
duration
);
this.start = checkNotNull(start);
this.duration = checkNotNull(duration);
this.vessel = checkNotNull(vessel);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* POSEIDON: an agent-based model of fisheries
* Copyright (c) 2024 CoHESyS Lab [email protected]
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package uk.ac.ox.poseidon.agents.behaviours.choices;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import one.util.streamex.EntryStream;
import uk.ac.ox.poseidon.agents.registers.Register;
import uk.ac.ox.poseidon.agents.registers.TransformedRegister;
import uk.ac.ox.poseidon.core.Factory;
import uk.ac.ox.poseidon.core.Simulation;
import uk.ac.ox.poseidon.core.SimulationScopeFactory;

import java.util.Map.Entry;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class BestOptionsRegisterFactory<O>
extends SimulationScopeFactory<Register<Entry<O, Double>>> {

Factory<? extends Register<? extends OptionValues<O>>> optionValuesRegister;

@Override
protected Register<Entry<O, Double>> newInstance(final Simulation simulation) {
return new TransformedRegister<>(
optionValuesRegister.get(simulation),
entryStream -> EntryStream
.of(entryStream)
.flatMapValues(optionValues -> optionValues.getBestEntries().stream())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@

import ec.util.MersenneTwisterFast;

import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;

import static com.google.common.base.Preconditions.checkNotNull;
import static uk.ac.ox.poseidon.core.MasonUtils.oneOf;
import static uk.ac.ox.poseidon.core.utils.Preconditions.checkUnitRange;

public class EpsilonGreedyChooser<O> implements Supplier<O> {

private final double epsilon;
private final OptionValues<O> optionValues;
private final Explorer<O> explorer;
private final Picker<O> explorer;
private final Picker<O> exploiter;
private final Evaluator<O> evaluator;
private final MersenneTwisterFast rng;
private O currentOption;
Expand All @@ -41,12 +41,14 @@ public class EpsilonGreedyChooser<O> implements Supplier<O> {
public EpsilonGreedyChooser(
final double epsilon,
final OptionValues<O> optionValues,
final Explorer<O> explorer,
final Picker<O> explorer,
final Picker<O> exploiter,
final Evaluator<O> evaluator,
final MersenneTwisterFast rng
) {
this.explorer = explorer;
this.optionValues = checkNotNull(optionValues);
this.explorer = explorer;
this.exploiter = exploiter;
this.evaluator = checkNotNull(evaluator);
this.epsilon = checkUnitRange(epsilon, "epsilon");
this.rng = checkNotNull(rng);
Expand All @@ -57,11 +59,13 @@ public O get() {
if (currentOption != null) {
optionValues.observe(currentOption, currentEvaluation.getResult());
}
final List<O> bestOptions = optionValues.getBestOptions();
final boolean explore = bestOptions.isEmpty() || rng.nextDouble() < epsilon;
currentOption = explore
? explorer.explore(currentOption)
: oneOf(bestOptions, rng);
final boolean explore = rng.nextBoolean(epsilon);
currentOption = Optional
.of(exploiter)
.filter(__ -> !explore)
.flatMap(Picker::pick)
.or(explorer::pick)
.orElseThrow(() -> new RuntimeException("Explorer did not find a valid option."));
currentEvaluation = evaluator.newEvaluation(currentOption);
return currentOption;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,23 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import uk.ac.ox.poseidon.core.Factory;
import uk.ac.ox.poseidon.agents.vessels.Vessel;
import uk.ac.ox.poseidon.agents.vessels.VesselScopeFactory;
import uk.ac.ox.poseidon.core.Simulation;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ExponentialMovingAverageOptionValuesFactory<O> implements Factory<OptionValues<O>> {
public class ExponentialMovingAverageOptionValuesFactory<O> extends VesselScopeFactory<OptionValues<O>> {

private double alpha;

@Override
public OptionValues<O> get(final Simulation simulation) {
protected OptionValues<O> newInstance(
final Simulation simulation,
final Vessel vessel
) {
return new ExponentialMovingAverageOptionValues<>(alpha);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

package uk.ac.ox.poseidon.agents.behaviours.choices;

public interface Explorer<O> {
O explore(final O currentOption);
import java.util.Optional;

@FunctionalInterface
public interface Picker<O> {
Optional<O> pick();
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,17 @@
import ec.util.MersenneTwisterFast;

import java.util.List;
import java.util.Optional;

import static com.google.common.base.Preconditions.checkNotNull;
import static uk.ac.ox.poseidon.core.MasonUtils.oneOf;
import static uk.ac.ox.poseidon.core.MasonUtils.upToOneOf;

public class RandomExplorer<O> implements Explorer<O> {
public class RandomPicker<O> implements Picker<O> {

private final ImmutableList<O> options;
private final MersenneTwisterFast rng;

public RandomExplorer(
public RandomPicker(
final List<O> options,
final MersenneTwisterFast rng
) {
Expand All @@ -41,7 +42,7 @@ public RandomExplorer(
}

@Override
public O explore(final O currentOption) {
return oneOf(options, rng);
public Optional<O> pick() {
return upToOneOf(options, rng);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@
import sim.util.Int2D;
import uk.ac.ox.poseidon.agents.behaviours.choices.EpsilonGreedyChooser;
import uk.ac.ox.poseidon.agents.behaviours.choices.Evaluator;
import uk.ac.ox.poseidon.agents.behaviours.choices.Explorer;
import uk.ac.ox.poseidon.agents.behaviours.choices.OptionValues;
import uk.ac.ox.poseidon.agents.behaviours.choices.Picker;
import uk.ac.ox.poseidon.agents.vessels.Vessel;
import uk.ac.ox.poseidon.agents.vessels.VesselScopeFactory;
import uk.ac.ox.poseidon.core.Factory;
import uk.ac.ox.poseidon.core.Simulation;

@Getter
Expand All @@ -40,8 +39,9 @@
public class EpsilonGreedyDestinationSupplierFactory extends VesselScopeFactory<DestinationSupplier> {

private double epsilon;
private Factory<? extends OptionValues<Int2D>> optionValues;
private VesselScopeFactory<? extends Explorer<Int2D>> explorer;
private VesselScopeFactory<? extends OptionValues<Int2D>> optionValues;
private VesselScopeFactory<? extends Picker<Int2D>> explorer;
private VesselScopeFactory<? extends Picker<Int2D>> exploiter;
private VesselScopeFactory<? extends Evaluator<Int2D>> destinationEvaluator;

@Override
Expand All @@ -51,8 +51,9 @@ protected DestinationSupplier newInstance(
) {
return new EpsilonGreedyChooser<>(
epsilon,
optionValues.get(simulation),
optionValues.get(simulation, vessel),
explorer.get(simulation, vessel),
exploiter.get(simulation, vessel),
destinationEvaluator.get(simulation, vessel),
simulation.random
)::get;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* POSEIDON: an agent-based model of fisheries
* Copyright (c) 2024 CoHESyS Lab [email protected]
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package uk.ac.ox.poseidon.agents.behaviours.destination;

import ec.util.MersenneTwisterFast;
import lombok.RequiredArgsConstructor;
import sim.util.Int2D;
import uk.ac.ox.poseidon.agents.behaviours.choices.OptionValues;
import uk.ac.ox.poseidon.agents.behaviours.choices.Picker;
import uk.ac.ox.poseidon.agents.registers.Register;
import uk.ac.ox.poseidon.agents.vessels.Vessel;
import uk.ac.ox.poseidon.geography.paths.GridPathFinder;

import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;

import static java.lang.Double.NEGATIVE_INFINITY;
import static java.util.Map.Entry.comparingByValue;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
import static one.util.streamex.MoreCollectors.maxAll;
import static uk.ac.ox.poseidon.core.MasonUtils.upToOneOf;

/**
* This picker will look for better options than the current option in a list of candidates to
* imitate and pick one at random if any of them are accessible from the vessel's current location.
* <p>
* Note that it is possible for this explorer to return {@code null}, in the case where
* {@code currentOption} is {@code null} and all the other candidates are also {@code null} (which
* can happen at the start of a simulation) or inaccessible (which can happen if the vessel is
* isolated in a lake on a random map). This explorer should therefore never be used as the final
* option in a chain of explorers; it needs another one to fall back on.
*/
@RequiredArgsConstructor
public class ImitatingCellPicker implements Picker<Int2D> {

private final Vessel vessel;
private final OptionValues<Int2D> optionValues;
private final Register<Entry<Int2D, Double>> candidateRegister;
private final GridPathFinder pathFinder;
private final MersenneTwisterFast rng;

@Override
public Optional<Int2D> pick() {

final Optional<Entry<Int2D, Double>> currentBestEntry =
optionValues.getBestEntry(rng);

final double currentBestValue =
currentBestEntry.map(Entry::getValue).orElse(NEGATIVE_INFINITY);

final List<Int2D> candidates = candidateRegister
.getAllEntries()
.map(Entry::getValue)
.filter(entry -> entry.getValue() > currentBestValue)
.filter(entry -> pathFinder.isAccessible(vessel.getCurrentCell(), entry.getKey()))
.collect(maxAll(comparingByValue(), mapping(Entry::getKey, toList())));

return upToOneOf(candidates, rng)
.or(() -> currentBestEntry.map(Entry::getKey));

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* POSEIDON: an agent-based model of fisheries
* Copyright (c) 2024 CoHESyS Lab [email protected]
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package uk.ac.ox.poseidon.agents.behaviours.destination;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import sim.util.Int2D;
import uk.ac.ox.poseidon.agents.behaviours.choices.OptionValues;
import uk.ac.ox.poseidon.agents.registers.Register;
import uk.ac.ox.poseidon.agents.vessels.Vessel;
import uk.ac.ox.poseidon.agents.vessels.VesselScopeFactory;
import uk.ac.ox.poseidon.core.Factory;
import uk.ac.ox.poseidon.core.Simulation;
import uk.ac.ox.poseidon.geography.paths.GridPathFinder;

import java.util.Map.Entry;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ImitatingCellPickerFactory extends VesselScopeFactory<ImitatingCellPicker> {

private VesselScopeFactory<? extends OptionValues<Int2D>> optionValues;
private Factory<? extends Register<Entry<Int2D, Double>>> candidateRegister;
private Factory<? extends GridPathFinder> pathFinder;

@Override
protected ImitatingCellPicker newInstance(
final Simulation simulation,
final Vessel vessel
) {
return new ImitatingCellPicker(
vessel,
optionValues.get(simulation, vessel),
candidateRegister.get(simulation),
pathFinder.get(simulation),
simulation.random
);
}
}
Loading

0 comments on commit 66d969c

Please sign in to comment.