From 59c9e320901a4eff5f074eb9fc4b81d88974ee96 Mon Sep 17 00:00:00 2001 From: Max Fierro Date: Tue, 7 May 2024 04:21:08 -0700 Subject: [PATCH] Progress on solver interfaces docs --- Cargo.lock | 241 +------------------------ Cargo.toml | 2 - src/game/zero_by/mod.rs | 4 +- src/solver/algorithm/strong/acyclic.rs | 8 +- src/solver/mod.rs | 218 +++++++++++++++------- 5 files changed, 166 insertions(+), 307 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ce8425..e3571a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,27 +65,6 @@ version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" - [[package]] name = "bitvec" version = "1.0.1" @@ -98,12 +77,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "bytemuck" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" - [[package]] name = "clap" version = "4.4.6" @@ -135,7 +108,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.57", + "syn", ] [[package]] @@ -150,33 +123,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "colored" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" -dependencies = [ - "is-terminal", - "lazy_static", - "windows-sys", -] - [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "errno" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" -dependencies = [ - "libc", - "windows-sys", -] - [[package]] name = "exitcode" version = "1.1.2" @@ -202,9 +154,7 @@ dependencies = [ "anyhow", "bitvec", "clap", - "colored", "exitcode", - "nalgebra", "petgraph", "regex", "serde_json", @@ -224,12 +174,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" - [[package]] name = "indexmap" version = "2.2.6" @@ -240,129 +184,18 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys", -] - [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" - -[[package]] -name = "linux-raw-sys" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" - -[[package]] -name = "matrixmultiply" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" -dependencies = [ - "autocfg", - "rawpointer", -] - [[package]] name = "memchr" version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" -[[package]] -name = "nalgebra" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307ed9b18cc2423f29e83f84fd23a8e73628727990181f18641a8b5dc2ab1caa" -dependencies = [ - "approx", - "matrixmultiply", - "nalgebra-macros", - "num-complex", - "num-rational", - "num-traits", - "simba", - "typenum", -] - -[[package]] -name = "nalgebra-macros" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91761aed67d03ad966ef783ae962ef9bbaca728d2dd7ceb7939ec110fffad998" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "num-complex" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - [[package]] name = "petgraph" version = "0.6.4" @@ -397,12 +230,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - [[package]] name = "regex" version = "1.9.6" @@ -432,19 +259,6 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" -[[package]] -name = "rustix" -version = "0.38.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - [[package]] name = "rustversion" version = "1.0.15" @@ -457,15 +271,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" -[[package]] -name = "safe_arch" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" -dependencies = [ - "bytemuck", -] - [[package]] name = "serde" version = "1.0.188" @@ -483,7 +288,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn", ] [[package]] @@ -497,19 +302,6 @@ dependencies = [ "serde", ] -[[package]] -name = "simba" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" -dependencies = [ - "approx", - "num-complex", - "num-traits", - "paste", - "wide", -] - [[package]] name = "strsim" version = "0.10.0" @@ -532,18 +324,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.57", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "syn", ] [[package]] @@ -563,12 +344,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - [[package]] name = "unicode-ident" version = "1.0.12" @@ -581,16 +356,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "wide" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68938b57b33da363195412cfc5fc37c9ed49aa9cfe2156fde64b8d2c9498242" -dependencies = [ - "bytemuck", - "safe_arch", -] - [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 947506c..57800dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,8 +25,6 @@ lto = "fat" clap = { version = "^4", features = ["derive"] } serde_json = "^1" exitcode = "^1" -nalgebra = "^0" -colored = "^2" anyhow = "^1" bitvec = "^1" regex = "^1" diff --git a/src/game/zero_by/mod.rs b/src/game/zero_by/mod.rs index 808e898..c47ea44 100644 --- a/src/game/zero_by/mod.rs +++ b/src/game/zero_by/mod.rs @@ -27,7 +27,7 @@ use crate::model::game::Variant; use crate::model::game::{Player, PlayerCount, State}; use crate::model::solver::SUtility; use crate::solver::algorithm::strong; -use crate::solver::{Extensive, SimpleUtility}; +use crate::solver::{Sequential, SimpleUtility}; /* SUBMODULES */ @@ -216,7 +216,7 @@ impl Forward for Session { /* SOLVING IMPLEMENTATIONS */ -impl Extensive for Session { +impl Sequential for Session { fn turn(&self, state: State) -> Player { let (turn, _) = self.decode_state(state); turn diff --git a/src/solver/algorithm/strong/acyclic.rs b/src/solver/algorithm/strong/acyclic.rs index 099c8a4..774e243 100644 --- a/src/solver/algorithm/strong/acyclic.rs +++ b/src/solver/algorithm/strong/acyclic.rs @@ -14,7 +14,7 @@ use crate::interface::IOMode; use crate::model::game::PlayerCount; use crate::model::solver::{IUtility, Remoteness}; use crate::solver::record::mur::RecordBuffer; -use crate::solver::{Extensive, IntegerUtility, RecordType}; +use crate::solver::{IntegerUtility, RecordType, Sequential}; use crate::util::Identify; /* SOLVERS */ @@ -27,7 +27,7 @@ where G: Transition + Bounded + IntegerUtility - + Extensive + + Sequential + Identify, { let db = volatile_database(game, mode) @@ -53,7 +53,7 @@ fn volatile_database( mode: IOMode, ) -> Result where - G: Extensive + Identify, + G: Sequential + Identify, { let id = game.id(); let db = volatile::Database::initialize(); @@ -80,7 +80,7 @@ fn backward_induction( ) -> Result<()> where D: KVStore, - G: Transition + Bounded + IntegerUtility + Extensive, + G: Transition + Bounded + IntegerUtility + Sequential, { let mut stack = Vec::new(); stack.push(game.start()); diff --git a/src/solver/mod.rs b/src/solver/mod.rs index 07362ad..7f7b36d 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -82,100 +82,196 @@ pub enum RecordType { /* STRUCTURAL INTERFACES */ -/// TODO -pub trait Extensive { - /// Returns the player `i` whose turn it is at the given `state`. The player - /// identifier `i` should never be greater than `N - 1`, where `N` is the - /// number of players in the underlying game. +/// Indicates that a discrete game is played sequentially, which allows for +/// representing histories as discrete [`State`]s. +pub trait Sequential { + /// Returns the player `i` whose turn it is at the given `state`, with + /// Player 0 always going first. + /// + /// TODO + /// + /// # Example + /// + /// TODO + /// + /// # Warning + /// + /// The player identifier `i` should never be greater than `N - 1`, where + /// `N` is the number of players in the game. Violating this will definitely + /// result in a program panic at some point. Unfortunately, there are not + /// many good ways of enforcing this restriction at compilation time. fn turn(&self, state: State) -> Player; - /// Returns the number of players in the underlying game. This should be at - /// least one higher than the maximum value returned by `turn`. + /// Returns the number of players in the underlying game. #[inline(always)] fn players(&self) -> PlayerCount { N } } -/// TODO -pub trait Composite -where - Self: Extensive, -{ - /// Returns a unique identifier for the partition that `state` is an element - /// of within the game variant specified by `self`. This implies no notion - /// of ordering between identifiers. +/// Indicates that a game can be partitioned into subsets that are convenient +/// for computation purposes. +pub trait Composite { + /// Returns an identifier for a subset of the space of states which `state` + /// belongs to. + /// + /// TODO + /// + /// # Example + /// + /// TODO fn partition(&self, state: State) -> Partition; - /// Provides an arbitrarily precise notion of the number of states that are - /// elements of `partition`. This can be used to distribute the work of - /// analyzing different partitions concurrently across different consumers - /// in a way that is equitable to improve efficiency. + /// Returns an estimate of the number of states in `partition`. + /// + /// It can be useful to know how many states are in a given partition for + /// practical purposes, such as distributing the load of exploring different + /// partitions in a balanced way; counting the states precisely would more + /// often than not defeat the purpose of saving computation. + /// + /// For many purposes, it is also enough for the return value of this + /// function to be accurate relative to other partitions, and not in actual + /// magnitude. The precise semantics of the output are left unspecified. + /// + /// # Example + /// + /// An example heuristic that could be used to estimate the number of states + /// in a game partition could be the amount of information required to be + /// able to differentiate among states within it. For example, if there are + /// more pieces on a board game partition than another, it is likely that it + /// has more states in it. fn size(&self, partition: Partition) -> StateCount; } /* UTILITY MEASURE INTERFACES */ -/// TODO +/// Indicates that a multiplayer game's players can only obtain utility values +/// that are real numbers. pub trait RealUtility { - /// If `state` is terminal, returns the utility vector associated with that - /// state, where `utility[i]` is the utility of the state for player `i`. If - /// the state is not terminal, it is recommended that this function panics. + /// Returns the utility vector associated with a terminal `state` whose + /// `i`'th entry is the utility of the state for player `i`. + /// + /// The behavior of this function is undefined in cases where `state` is not + /// terminal. No assumptions are made about the possible utility values that + /// players can obtain through playing the game, except that they can be + /// represented with real numbers (see [`RUtility`]). + /// + /// # Example + /// + /// An extreme example of such a game would be a discrete auction, where + /// players can gain arbitrary amounts of money, but can only act at known + /// discrete points in time. fn utility(&self, state: State) -> [RUtility; N]; } -/// TODO +/// Indicates that a multiplayer game's players can only obtain utility values +/// that are integers. pub trait IntegerUtility { - /// If `state` is terminal, returns the utility vector associated with that - /// state, where `utility[i]` is the utility of the state for player `i`. If - /// the state is not terminal it is recommended that this function panics. + /// Returns the utility vector associated with a terminal `state` where + /// whose `i`'th entry is the utility of the state for player `i`. + /// + /// The behavior of this function is undefined in cases where `state` is not + /// terminal. No assumptions are made about the possible utility values that + /// players can obtain through playing the game, except that they can be + /// represented with integers; the [`IUtility`] type serves this purpose. + /// + /// # Example + /// + /// An extreme example of such a game would be ten people fighting over + /// each other's coins. Since the coins are discrete, it is only possible + /// to gain utility in specific increments. We can model this hypothetical + /// game through this interface. fn utility(&self, state: State) -> [IUtility; N]; } -/// TODO +/// Indicates that a multiplayer game's players can only obtain utility values +/// within specific known categories. pub trait SimpleUtility { - /// If `state` is terminal, returns the utility vector associated with that - /// state, where `utility[i]` is the utility of the state for player `i`. If - /// the state is not terminal, it is recommended that this function panics. + /// Returns the utility vector associated with a terminal `state` where + /// whose `i`'th entry is the utility of the state for player `i`. + /// + /// The behavior of this function is undefined in cases where `state` is not + /// terminal. This assumes that utility players' utility values can only + /// be within the following categories: + /// * [`SUtility::Lose`] + /// * [`SUtility::Draw`] + /// * [`SUtility::Tie`] + /// * [`SUtility::Win`] + /// + /// # Example + /// + /// In a 6-player game of Chinese Checkers, it is possible for one player + /// to obtain a [`SUtility::Win`] by finishing first (in the event where + /// utility is defined without 2nd through 6th places), and everyone else + /// would be assigned a [`SUtility::Lose`]. fn utility(&self, state: State) -> [SUtility; N]; } /* UTILITY STRUCTURE INTERFACES */ -/// Indicates that a game is 2-player, simple-sum, and zero-sum; this restricts -/// the possible utilities for a position to the following cases: -/// * `[Draw, Draw]` -/// * `[Lose, Win]` -/// * `[Win, Lose]` -/// * `[Tie, Tie]` -/// -/// Since either entry determines the other, knowing one of the entries and the -/// turn information for a given state provides enough information to determine -/// both players' utilities. +/// Indicates that a game is 2-player, has categorical utility values, and is +/// zero-sum, allowing the implementer to eschew providing a player's utility. pub trait ClassicGame { - /// If `state` is terminal, returns the utility of the player whose turn it - /// is at that state. If the state is not terminal, it is recommended that - /// this function panics. + /// Returns the utility of the only player whose turn it is at `state`. + /// + /// This assumes that `state` is terminal, that the underlying game is + /// two-player and zero-sum. In other words, the only attainable pairs of + /// utility values should be the following: + /// * [`SUtility::Draw`] and [`SUtility::Draw`] + /// * [`SUtility::Lose`] and [`SUtility::Win`] + /// * [`SUtility::Win`] and [`SUtility::Lose`] + /// * [`SUtility::Tie`] and [`SUtility::Tie`] + /// + /// # Example + /// + /// This game category is fairly intuitive in that most two-player board + /// games fall into it. For example, in a game of Chess a [`SUtility::Win`] + /// is recognized to be the taking of the opponent's king, where this also + /// implies that the player who lost it is assigned [`SUtility::Lose`], and + /// where any other ending is classified as a [`SUtility::Tie`]. + /// + /// In other games where it is possible to play forever, both players can + /// achieve a [`SUtility::Draw`] by constantly avoiding reaching a terminal + /// state (perhaps motivated by avoiding realizing imminent losses). + /// + /// # Warning + /// + /// While the games that implement this interface should be zero-sum, the + /// type system is not sufficiently rich to enforce such a constraint at + /// compilation time, so sum specifications are generally left to semantics. fn utility(&self, state: State) -> SUtility; } -/// Indicates that a game is a puzzle with simple outcomes. This implies that it -/// is 1-player and the only possible utilities obtainable for the player are: -/// * `Lose` -/// * `Draw` -/// * `Tie` -/// * `Win` -/// -/// A winning state is usually one where there exists a sequence of moves that -/// will lead to the puzzle being fully solved. A losing state is one where any -/// sequence of moves will always take the player to either another losing state -/// or a state with no further moves available (with the puzzle still unsolved). -/// A draw state is one where there is no way to reach a winning state but it is -/// possible to play forever without reaching a losing state. A tie state is any -/// state that does not subjectively fit into any of the above categories. +/// Provides a method to find the utility of terminal values in "classic" +/// puzzles, which are single-player games with categorical [`SUtility`] values. pub trait ClassicPuzzle { - /// If `state` is terminal, returns the utility of the puzzle's player. If - /// the state is not terminal, it is recommended that this function panics. + /// Returns the utility of the only player in the puzzle at `state`. + /// + /// This assumes that `state` is terminal. The utility structure implies + /// that the game is 1-player, and that the only utility values attainable + /// for the player are: + /// * [`SUtility::Lose`] + /// * [`SUtility::Draw`] + /// * [`SUtility::Tie`] + /// * [`SUtility::Win`] + /// + /// # Example + /// + /// As a theoretical example, consider a Rubik's Cube. Here, we say that + /// the solved state of the cube is a [`SUtility::Win`] for the player, as + /// they have completed their objective. + /// + /// Now consider a crossword puzzle where you cannot erase words. It would + /// be possible for the player to achieve a [`SUtility::Lose`] by filling + /// out incorrect words and having no possible words left to write. + /// + /// For a [`SUtility::Draw`], we can consider a puzzle where it is possible + /// for a loss to be the only material option, but for there to also be the + /// option to continue playing forever (as to not realize this outcome). + /// + /// Finally, a [`SUtility::Tie`] can be interpreted as reaching an outcome + /// of the puzzle where it is impossible to back out of, but that presents + /// no positive or negative impact on the player. fn utility(&self, state: State) -> SUtility; } @@ -213,7 +309,7 @@ where impl SimpleUtility<2, B> for G where - G: Extensive<2, B> + ClassicGame, + G: Sequential<2, B> + ClassicGame, { fn utility(&self, state: State) -> [SUtility; 2] { let mut sutility = [SUtility::Tie; 2];