mirror of
https://github.com/tig-pool-nk/tig-monorepo.git
synced 2026-02-21 17:47:22 +08:00
up
This commit is contained in:
parent
aca619aff8
commit
fe51d81c2f
@ -4,16 +4,19 @@ This repository contains the implementation of The Innovation Game (TIG).
|
||||
|
||||
## Important Links
|
||||
|
||||
* [TIG Documentation](https://docs.tig.foundation/)
|
||||
* [TIG Whitepaper](docs/whitepaper.pdf)
|
||||
* [TIG Tech Explainer](docs/tech/1_basics.md)
|
||||
* [TIG Licensing Explainer](docs/guides/anatomy.md)
|
||||
* [Getting Started with Innovating](docs/guides/innovating.md)
|
||||
* [Challenge Descriptions](docs/challenges/satisfiability.md)
|
||||
* [Implementations vs Breakthroughs](docs/guides/breakthroughs.md)
|
||||
* [Voting Guidelines for Token Holders](docs/guides/voting.md)
|
||||
|
||||
## Repo Contents
|
||||
|
||||
* [tig-algorithms](./tig-algorithms/README.md) - A Rust crate that hosts algorithm submissions made by Innovators in TIG
|
||||
* [tig-benchmarker](./tig-benchmarker/README.md) - Python scripts for running TIG's benchmarker in master/slave configuration
|
||||
* [tig-breakthroughs](./tig-breakthroughs/README.md) - A folder that hosts submissions of algorithmic methods made by Innovators in TIG.
|
||||
* [tig-challenges](./tig-challenges/README.md) - A Rust crate that contains the implementation of TIG's challenges (computational problems adapted for proof-of-work)
|
||||
* [tig-protocol](./tig-protocol/README.md) - A Rust crate that contains the implementation of TIG's core protocol logic.
|
||||
* [tig-structs](./tig-structs/README.md) - A Rust crate that contains the definitions of structs used throughout TIG
|
||||
|
||||
Binary file not shown.
BIN
docs/agreements/invention_assignment.doc
Normal file
BIN
docs/agreements/invention_assignment.doc
Normal file
Binary file not shown.
341
docs/guides/breakthroughs.md
Normal file
341
docs/guides/breakthroughs.md
Normal file
@ -0,0 +1,341 @@
|
||||
# Rewarding Innovation in The Innovation Game
|
||||
|
||||
## Introduction
|
||||
|
||||
The rewards for innovation in The Innovation Game (the "**Game**") are
|
||||
designed to be **sufficient** to incentivise innovators to submit their
|
||||
innovation ("**Contributions**") to the Game on the understanding that
|
||||
the Contributions will be made available to others as the basis for
|
||||
**open innovation** under the *TIG Open Data License*, and for further
|
||||
innovation by participants in the Game under the terms of the *TIG
|
||||
Innovator Outbound Game License*.
|
||||
|
||||
By **sufficiency** we mean that the reward must be at least enough to
|
||||
cause commercially valuable innovation (by implication, what is
|
||||
commercially valuable should, at least, be novel over the state of the
|
||||
art, innovative and free from encumbrances) to be submitted to the Game.
|
||||
What is sufficient reward may not necessarily be equitable in terms of
|
||||
fully compensating an innovator for their contribution of value (that is
|
||||
almost impossible to determine at the point of submission of the
|
||||
Contribution), at least not immediately, it just needs to be enough to
|
||||
cause the innovator to submit their Contribution.
|
||||
|
||||
In an ideal world the rewards to innovators would be exactly
|
||||
commensurate with the value added by an innovator's Contribution.
|
||||
Because the Game seeks to reward Contributors for improved algorithm
|
||||
performance, it is intuitively attractive to look for a method of
|
||||
quantitatively measuring algorithmic performance but this would add
|
||||
significant (and what we consider unnecessary) complexity and also fail
|
||||
to recognise a number of realities including, the difficulties of
|
||||
objectively measuring and verifying the quantum of immediate commercial
|
||||
value of a particular Contribution, its spill over value and any latent
|
||||
value realised only later in time after the rewards have been assessed.
|
||||
Accordingly, we have decided, for now, to keep it as simple as possible.
|
||||
We will seek only what is **sufficient** in terms of reward to an
|
||||
innovator to compensate them for their effort and ingenuity to
|
||||
incentivise them to submit their innovation to the Game. This assessment
|
||||
of reward may not necessarily look objectively fair in relative terms
|
||||
(when comparing one algorithm to another), particularly in hindsight,
|
||||
but any apparent inequity is mitigated by the fact that the value of the
|
||||
TIG token will represent **all** off the accrued value of all
|
||||
Contributions over time and so each innovator will have the chance to
|
||||
benefit (if they hold some or all of their tokens) from the value
|
||||
created not just by their Contribution but also that from the
|
||||
Contributions of everyone else throughout the life of the Game.
|
||||
|
||||
One of the most obvious rewards offered by the Innovation Game for
|
||||
Contributions is the TIG token. Whether the token rewards are sufficient
|
||||
to incentivise the submission of Contributions will depend on the token
|
||||
price prevailing at the time of submission, the number of challenges to
|
||||
which rewards are allocated and, also to some extent, on the expected
|
||||
future value of the token. Some submissions will also be motivated by
|
||||
other forms of reward such as academic impact.
|
||||
|
||||
Because the token price will vary from time to time, we will only know
|
||||
if, as the basis of reward it is sufficient, by gathering empirical
|
||||
evidence provided by the quality of submissions in practice, and by
|
||||
feedback from potential innovators. For this reason, the Team will
|
||||
always be on alert to adjusting the rewards for the Game to make them
|
||||
sufficient. Whilst the protocol is still in development innovator
|
||||
rewards will be under constant review.
|
||||
|
||||
If an innovator decides to withhold their innovation from the Game
|
||||
because they assess that the reward is not sufficient incentive, they
|
||||
will be exposed to the jeopardy that, whilst they wait, someone else may
|
||||
submit an equivalent or better Contribution and resultingly then be
|
||||
deprived of the rewards that they may have earned themselves by
|
||||
submitting their algorithm.
|
||||
|
||||
A fundamental cornerstone of the Game is the synthetic market that is
|
||||
created by incentivising benchmarkers to create demand for the most
|
||||
performant algorithm implementations. The value of a Contribution is
|
||||
measured in that market by benchmarkers; the more a Contribution is
|
||||
adopted, the more reward is allocated to it. If a change of a single
|
||||
line of code in an existing code implementation (whether it relates to a
|
||||
change to the algorithmic method or to a code optimisation) makes
|
||||
sufficient difference that benchmarkers, acting rationally to improve
|
||||
their performance in the Game, adopt it instead of other available
|
||||
alternatives, then the market is designed to reward that change.
|
||||
|
||||
In the context of the Game, with the exception below related to the TIG
|
||||
Game Rules\*, it is crooked thinking to simply assess the quantum of
|
||||
lines of code present or changed for determining the appropriateness for
|
||||
reward or what the quantum of that reward should be. If the synthetic
|
||||
market is functioning properly the best algorithm implementation will
|
||||
prevail and an enquiry into "why" it is prevailing is not relevant. It
|
||||
is the job of the Team to ensure that the synthetic market is the
|
||||
closest proxy that it can be for rewarding the algorithmic performance
|
||||
and utility most valued by commercial enterprise.
|
||||
|
||||
\**The TIG Game Rules do mandate that a Contribution must make a
|
||||
"meaningful difference" over existing implementations in the Game. This
|
||||
will stop straightforward plagiarism where no or insignificant
|
||||
additional value is added by a Contributor and this aspect of the Game
|
||||
Rules will be policed.*
|
||||
|
||||
## Defining and Classifying Innovation
|
||||
|
||||
Generally, improvements in algorithmic methods tend to yield exponential
|
||||
or order-of-magnitude efficiency gains by changing the problem-solving
|
||||
approach, while the way in which algorithmic methods are implemented in
|
||||
code tend to provide incremental improvements. It is important
|
||||
therefore, that TIG places emphasis on incentivising the submission of
|
||||
innovative algorithmic methods. TIG intends to do that by introducing a
|
||||
process for identifying innovative algorithmic methods that will then be
|
||||
eligible for additional rewards.
|
||||
|
||||
We propose to distinguish two different types of Contribution in the
|
||||
context of the Game and rewarding them differently: (1) **Algorithm
|
||||
Implementations** (code); and (2) **Breakthrough Algorithms**
|
||||
(methods).
|
||||
|
||||
Implementations of algorithms comprise two elements; **(i)** an
|
||||
algorithmic method (the fundamental approach or strategy for solving a
|
||||
problem, independent of specific code or language); and **(ii)** an
|
||||
expression of the algorithmic method in code.
|
||||
|
||||
In the Game the algorithmic method element may be a Breakthrough
|
||||
Algorithm or an algorithmic method that is not a Breakthrough Algorithm.
|
||||
|
||||
### Breakthrough Algorithms
|
||||
|
||||
TIG believes that the determination of whether an algorithmic method is
|
||||
a Breakthrough Algorithm should be done by a combination of; **(i)** a
|
||||
token weighted vote assessing novelty and inventiveness\*; and **(ii)**
|
||||
an assessment of the performance of the algorithmic method determined
|
||||
solely by the extent of its adoption by benchmarkers in the "TIG
|
||||
synthetic market" (see: *Accessing Breakthrough Rewards* below).
|
||||
|
||||
\* *For a discussion and explanation of novelty and inventiveness in the
|
||||
context of the Game see [Breakthrough Rewards Guide for
|
||||
Token Holders](./voting.md).*
|
||||
|
||||
### Algorithm Implementations
|
||||
|
||||
Algorithm Implementations are expressions of algorithmic methods in code
|
||||
and in the context of the Game they may be an expression of an
|
||||
algorithmic method which is a Breakthrough Algorithm or an expression of
|
||||
an algorithmic method that is not a Breakthrough Algorithm.
|
||||
|
||||
## Token Rewards for Innovation
|
||||
|
||||
To reflect the patentability and significant impact on performance that
|
||||
innovative algorithmic methods can have compared with algorithm
|
||||
implementations, TIG will offer additional rewards for Breakthrough
|
||||
Algorithms. These additional rewards (so called "**Breakthrough
|
||||
Rewards")**, are brought about through the design of the protocol
|
||||
enabling the rewards for Breakthrough Algorithms to persist for multiple
|
||||
Rounds where they continue to provide the basis of algorithm
|
||||
implementations adopted by benchmarkers.
|
||||
|
||||
An Algorithm Implementation which does not embody an algorithmic method
|
||||
which is eligible for potential Breakthrough Rewards, only rewards the
|
||||
innovator that submitted the Algorithm Implementation with Standard
|
||||
Rewards (subject to satisfaction of adoption thresholds). Because of the
|
||||
absence of a Breakthrough Algorithm, Breakthrough Rewards notionally
|
||||
allocated for Breakthrough Algorithms will, instead, be allocated to the
|
||||
TIG treasury and used to bootstrap Breakthrough Algorithm development
|
||||
(these are referred to in the Game as "**Bootstrap Rewards**").
|
||||
|
||||
For Algorithm Implementations that embody an algorithmic method which is
|
||||
eligible for potential Breakthrough Rewards, two types of reward are
|
||||
potentially available (subject to satisfaction of adoption thresholds):
|
||||
|
||||
- **Standard Rewards** will be available for the innovator that
|
||||
submitted the adopted implementation of the Breakthrough Algorithm.
|
||||
|
||||
- **Breakthrough Rewards** will be available for the innovator that
|
||||
submitted the Breakthrough Algorithm embodied in the implementation.
|
||||
|
||||
The innovator(s) earning Standard Rewards and Breakthrough Rewards in
|
||||
respect of an Algorithm Implementation may be the same or different
|
||||
entities. An innovator can earn both Standard Rewards and Breakthrough
|
||||
Rewards simultaneously or at different times.
|
||||
|
||||
### Standard Rewards for Algorithm Implementations
|
||||
|
||||
> **15%** of the total rewards available in a Round will be allocated to
|
||||
> Standard Rewards for Algorithm Implementations.
|
||||
|
||||
### Breakthrough Rewards for Breakthrough Algorithms
|
||||
|
||||
> **15%** of the total rewards available in a Round will be allocated to
|
||||
> Breakthrough Rewards for Breakthrough Algorithms.
|
||||
|
||||
The most significant difference between Standard Rewards and
|
||||
Breakthrough Rewards is that the Breakthrough Rewards will continue to
|
||||
be earned where the Breakthrough Algorithm is inherited by an
|
||||
implementation, meaning that Breakthrough Rewards can persist for longer
|
||||
because they will be earned in connection with any, and all adopted
|
||||
implementations of that Breakthrough Algorithm. Standard Rewards will be
|
||||
earned only for as long as the respective implementation is adopted by
|
||||
benchmarkers. Notwithstanding that the allocations of rewards are prima
|
||||
facie equal for both Breakthrough Rewards and Standard Rewards (at 15%
|
||||
each), because of the factor of persistence of adoption in calculating
|
||||
aggregate rewards over time, Breakthrough Rewards in respect of
|
||||
significant algorithmic breakthroughs are likely to far exceed Standard
|
||||
Rewards.
|
||||
|
||||
Breakthrough Rewards are expected to be greater than Standard Rewards to
|
||||
reflect four attributes of Breakthrough Algorithms;
|
||||
|
||||
(i) they are potentially patentable (bringing greater commercial value
|
||||
to the TIG ecosystem);
|
||||
|
||||
(ii) they are novel (there is no novelty test in the Game for Code
|
||||
Optimisations which means they may have lower commercial value than
|
||||
Breakthrough Algorithms);
|
||||
|
||||
(iii) they generally have greater potential to make significant
|
||||
performance improvements over the state of the art (bringing
|
||||
greater value to the TIG ecosystem); and
|
||||
|
||||
(iv) they are more certain to have value extrinsic to the Game.
|
||||
|
||||
It is the Team's belief that sufficiency of reward can be achieved in
|
||||
respect of Standard Rewards by appropriately setting the period during
|
||||
which a Code Optimisation will be guaranteed not to be used as the basis
|
||||
for a subsequent Code Optimisation contributed by a third party. At
|
||||
present this period is two Rounds. Only the behaviour of innovators can
|
||||
ultimately signal to us whether, at a minimum, this "protected" period
|
||||
for a Code Optimisation is sufficient to incentivise its submission to
|
||||
the game and we will be monitoring this as the protocol develops.
|
||||
|
||||
## Accessing Breakthrough Rewards
|
||||
|
||||
If an Innovator believes that their Contribution is or embodies a
|
||||
potential Breakthrough Algorithm, then they may request that their
|
||||
Contribution is considered for approval as a Breakthrough Algorithm. For
|
||||
a Contribution to be a Breakthrough Algorithm, the following must
|
||||
**ALL** be satisfied with respect to the Contribution:
|
||||
|
||||
(i) It must be declared, by Token Holder Vote that the Algorithm
|
||||
implementation subject to review embodies an algorithmic method that
|
||||
is eligible for potential Breakthrough Rewards;
|
||||
|
||||
(ii) The intellectual property rights embodied in the Contribution must
|
||||
be irrevocably assigned to TIG in accordance with the TIG IP
|
||||
Policy;
|
||||
|
||||
(iii) The Contributor must burn two hundred and fifty (250) TIG tokens
|
||||
for each requested Token Holder Vote; and
|
||||
|
||||
(iv) In a rolling 1 week window the sum of all algorithm implementations
|
||||
which embody the algorithmic method subject to review must achieve
|
||||
a sufficient degree of adoption by Benchmarkers (where for Standard
|
||||
rewards the adoption threshold by benchmarkers is presently 25%
|
||||
before the algorithm implementation is merged to obtain Standard
|
||||
Rewards, the corresponding adoption threshold by benchmarkers for
|
||||
potential Breakthrough Algorithms will be 50%). The reason why the
|
||||
threshold of adoption is higher for potential Breakthrough
|
||||
Algorithms is that TIG wishes to reserve Breakthrough Rewards for
|
||||
algorithmic improvements that offer a significant universal
|
||||
improvement in performance (which will be valued more highly by
|
||||
commercial licensees).
|
||||
|
||||
Essentially the **Token Holder Vote** is assessing the novelty and
|
||||
inventiveness of a contributed algorithmic method. To maximise the
|
||||
likelihood that a Breakthrough Algorithm will be ratified by Token
|
||||
Holder Vote, an innovator seeking Breakthrough Rewards is advised to
|
||||
disclose the following information so that it may be assessed by token
|
||||
holders:
|
||||
|
||||
- **Novelty:** Provide results of a prior art search to identify any
|
||||
existing disclosures that might affect the novelty of your algorithmic
|
||||
method.
|
||||
|
||||
- **Inventiveness:** Clearly document how the algorithmic method differs
|
||||
from existing solutions (i.e. is non-obvious) and any new technical
|
||||
effect it achieves.
|
||||
|
||||
- **Technical Effect:** Document of how the algorithmic method offers
|
||||
the potential to provide the basis for significant technical
|
||||
advancements. Document how the algorithmic method might have **real
|
||||
world application** e.g. in fields such as computer security, medical
|
||||
imaging, autonomous systems. or data compression.
|
||||
|
||||
## Encumbrances
|
||||
|
||||
The value of Contributions to TIG will be compromised if the
|
||||
Contributions are encumbered by third party intellectual property rights
|
||||
and terms of the TIG Inbound Game License seeks to deal with this.
|
||||
|
||||
The terms of the TIG Inbound Game License state:
|
||||
|
||||
"Submitter represents that Submitter is legally entitled to grant the
|
||||
above licenses and that, to the best of Submitter's knowledge, the Work
|
||||
does not infringe any rights of a third party."
|
||||
|
||||
## Attribution of Copyright Owner
|
||||
|
||||
The header file for each algorithm implementation submitted to the Game
|
||||
must comply with the terms of the TIG Inbound Game License. This
|
||||
establishes the identity of the copyright owner (which may or may not be
|
||||
the original author of the code) of the implementation of the
|
||||
algorithmic method in source code.
|
||||
|
||||
## Unique Algorithmic Method Identifier (UAI)
|
||||
|
||||
With the introduction of Breakthrough Rewards, the significance of the
|
||||
correct identification of the original creator of the algorithmic method
|
||||
embodied in an algorithm implementation has even greater significance.
|
||||
|
||||
It is important that the identification is not only correct, but that it
|
||||
appears in all algorithm implementations which embody the relevant
|
||||
algorithmic method.
|
||||
|
||||
The copyright notice does not necessarily identify the original creator
|
||||
of the algorithmic method embodied in the implementation (because the
|
||||
creator of the algorithmic method and the author of the code
|
||||
implementing the algorithmic method may be different). For this reason,
|
||||
the header file must also include information which identifies the
|
||||
creator of the algorithmic method. To do this TIG will issue a Unique
|
||||
Algorithmic Method Identifier (UAI) for any algorithmic method submitted
|
||||
to TIG. This UAI will be included in the header file of the algorithm
|
||||
implementation embodying the algorithmic method before it is published
|
||||
by TIG. The UAI will enable TIG to identify which algorithmic methods
|
||||
are eligible for Breakthrough Rewards and which ones are not.
|
||||
|
||||
The Game Rules will mandate that where an Algorithm Implementation
|
||||
(Algorithm B) is a modified implementation of an existing Algorithm
|
||||
Implementation (Algorithm A) and continues to embody an algorithmic
|
||||
method substantially similar to that embodied in Algorithm A, if
|
||||
Algorithm A has an associated UAI then that UAI must be entered into the
|
||||
header file of Algorithm B. This should create a correct indication of
|
||||
the heritage of the code back to the original creator of the relevant
|
||||
embodied algorithmic method.
|
||||
|
||||
There will be a very strong incentive for innovators that benefit from
|
||||
Breakthrough Rewards to ensure that they protect the persistence of the
|
||||
Breakthrough Rewards associated with their Breakthrough Algorithm, by
|
||||
carefully scrutinising the novelty of any challenger algorithmic methods
|
||||
submitted to TIG.
|
||||
|
||||
## Attribution of Contributor
|
||||
|
||||
The header file must also include an attribution of the name of the
|
||||
Contributor (referred to as "Submitter" in the TIG Inbound Game License)
|
||||
of the Contribution and the Game Rules will be amended such that any
|
||||
code based on the work submitted by that Contributor is required to
|
||||
attribute the Contributor thus creating an indication of the heritage of
|
||||
the code back to the original Contributor.
|
||||
@ -69,9 +69,13 @@ git pull public <branch>
|
||||
2. Make a copy of `tig-algorithms/<challenge_name>/template.rs` or an existing algorithm (see notes)
|
||||
3. Make sure your file has the following notice in its header if you intend to submit it to TIG:
|
||||
```
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright [year copyright work created] [name of copyright owner]
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
Identity of Submitter [name of person or entity that submits the Work to TIG]
|
||||
|
||||
UAI [UAI (if applicable)]
|
||||
|
||||
Licensed under the TIG Inbound Game License v2.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
@ -82,6 +86,10 @@ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
```
|
||||
* If your implementation is based on an algorithmic method submitted to TIG, you must attribute your implementation to it (example UAI: `c001_b001`)
|
||||
* UAI of a method is detailed inside `tig-breakthroughs/<challenge_name>/<method_name>.md`
|
||||
* Methods have branch name `<challenge_name>/method/<method_name>`
|
||||
* If your implementation is based on an algorithmic method outside of TIG, set UAI to `null`
|
||||
4. Rename the file with your own `<algorithm_name>`
|
||||
5. Edit `tig-algorithms/<challenge_name>/mod.rs` to export your algorithm and test it:
|
||||
```
|
||||
@ -141,95 +149,8 @@ language governing permissions and limitations under the License.
|
||||
* If you are copying and modifying an algorithm that has been submitted to TIG, make sure to use the `innovator_outbound` version
|
||||
* Do not include tests in your algorithm file. TIG will reject your algorithm submission.
|
||||
* Only your algorithm's rust code gets submitted. You should not be modifying `Cargo.toml` in `tig-algorithms`. Any extra dependencies you add will not be available when TIG compiles your algorithm
|
||||
* If you need to use random number generation be sure to use `let mut rng = StdRng::seed_from_u64(challenge.seed as u64)` to ensure your algorithm is deterministic.
|
||||
* To test cuda, edit the following test, and use the command `cargo test -p tig-algorithms --features cuda -- --nocapture`:
|
||||
```
|
||||
#[cfg(feature = "cuda")]
|
||||
#[cfg(test)]
|
||||
mod cuda_tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use cudarc::nvrtc::compile_ptx;
|
||||
use std::{sync::Arc, collections::HashMap};
|
||||
use tig_challenges::{<challenge_name>::*, *};
|
||||
|
||||
fn load_cuda_functions(
|
||||
dev: &Arc<CudaDevice>,
|
||||
kernel: &CudaKernel,
|
||||
key: &str,
|
||||
) -> HashMap<&'static str, CudaFunction> {
|
||||
let start = std::time::Instant::now();
|
||||
println!("Compiling CUDA kernels for {}", key);
|
||||
let ptx = compile_ptx(kernel.src).expect("Cuda Kernel failed to compile");
|
||||
dev.load_ptx(ptx, key, &kernel.funcs)
|
||||
.expect("Failed to load CUDA functions");
|
||||
let funcs = kernel
|
||||
.funcs
|
||||
.iter()
|
||||
.map(|&name| (name, dev.get_func(key, name).unwrap()))
|
||||
.collect();
|
||||
println!(
|
||||
"CUDA kernels for '{}' compiled in {}ms",
|
||||
key,
|
||||
start.elapsed().as_millis()
|
||||
);
|
||||
funcs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cuda_<algorithm_name>() {
|
||||
let dev = CudaDevice::new(0).expect("Failed to create CudaDevice");
|
||||
let challenge_cuda_funcs = match &<challenge_name>::KERNEL {
|
||||
Some(kernel) => load_cuda_functions(&dev, &kernel, "challenge"),
|
||||
None => {
|
||||
println!("No CUDA kernel for challenge");
|
||||
HashMap::new()
|
||||
}
|
||||
};
|
||||
let algorithm_cuda_funcs = match &<algorithm_name>::KERNEL {
|
||||
Some(kernel) => load_cuda_functions(&dev, &kernel, "algorithm"),
|
||||
None => {
|
||||
println!("No CUDA kernel for algorithm");
|
||||
HashMap::new()
|
||||
}
|
||||
};
|
||||
|
||||
let difficulty = Difficulty {
|
||||
// Uncomment the relevant fields.
|
||||
// Modify the values for different difficulties
|
||||
|
||||
// -- satisfiability --
|
||||
// num_variables: 50,
|
||||
// clauses_to_variables_percent: 300,
|
||||
// -- vehicle_routing --
|
||||
// num_nodes: 40,
|
||||
// better_than_baseline: 250,
|
||||
|
||||
// -- knapsack --
|
||||
// num_items: 50,
|
||||
// better_than_baseline: 10,
|
||||
|
||||
// -- vector_search --
|
||||
// num_queries: 10,
|
||||
// better_than_baseline: 350,
|
||||
};
|
||||
let seed = [0u8; 32]; // change this to generate different instances
|
||||
let challenge =
|
||||
Challenge::cuda_generate_instance(seed, &difficulty, &dev, challenge_cuda_funcs)
|
||||
.unwrap();
|
||||
match <algorithm_name>::cuda_solve_challenge(&challenge, &dev, algorithm_cuda_funcs) {
|
||||
Ok(Some(solution)) => match challenge.verify_solution(&solution) {
|
||||
Ok(_) => println!("Valid solution"),
|
||||
Err(e) => println!("Invalid solution: {}", e),
|
||||
},
|
||||
Ok(None) => println!("No solution"),
|
||||
Err(e) => println!("Algorithm error: {}", e),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
* If you need to use random number generation, ensure that it is seeded so that your algorithm is deterministic.
|
||||
* Suggest to use `let mut rng = SmallRng::from_seed(StdRng::from_seed(challenge.seed).gen())`
|
||||
|
||||
## Locally Compiling Your Algorithm into WASM
|
||||
|
||||
@ -251,7 +172,8 @@ git push origin <challenge_name>/<algorithm_name>
|
||||
|
||||
## Making Your Submission
|
||||
|
||||
You will need to burn 0.001 ETH to make a submission. Visit https://play.tig.foundation/innovator and follow the instructions.
|
||||
|
||||
10 TIG will be deducted from your Available Fee Balance to make a submission. You can topup via the [Benchmarker page](https://play.tig.foundation/benchmarker)
|
||||
|
||||
**IMPORTANT:**
|
||||
* Submissions are final and cannot be modified after they are made
|
||||
|
||||
426
docs/guides/voting.md
Normal file
426
docs/guides/voting.md
Normal file
@ -0,0 +1,426 @@
|
||||
|
||||
# Breakthrough Rewards Guide for Token Holders
|
||||
|
||||
## SUMMARY
|
||||
|
||||
- **Vote on whether an algorithmic method is eligible to earn potential Breakthrough Rewards.**
|
||||
|
||||
- **One token one vote.**
|
||||
|
||||
- **Assumed that vote will be exercised to maximise token value.**
|
||||
|
||||
- **Token holders** determine patentability through an assessment of novelty and inventiveness
|
||||
|
||||
- **Benchmarkers** determine performance through adoption (the “TIG synthetic market”)
|
||||
|
||||
- **Relevant considerations for token holders:**
|
||||
|
||||
- **Distinguishing algorithmic method from algorithmic implementation**
|
||||
|
||||
- **Assessing algorithmic method for novelty**
|
||||
|
||||
- **Assessing algorithmic method for inventiveness**
|
||||
|
||||
- **Assessing the impact of your voting decision on token value**
|
||||
|
||||
## What are Breakthrough Rewards and why does TIG offer them ?
|
||||
|
||||
- Breakthrough Rewards are a notional class of TIG token reward that is reserved for rewarding ONLY innovation in algorithmic methods.
|
||||
|
||||
- The efficiency gains from improved algorithmic methods versus code optimizations in implementations can differ greatly in impact and scale.
|
||||
|
||||
**Performance**
|
||||
|
||||
- Generally, improvements in algorithmic methods tend to yield exponential or order-of-magnitude efficiency gains by changing the problem-solving approach, while code optimizations tend to provide incremental improvements by refining an existing algorithm's implementation [*For illustrative examples see Appendix 1*]. It is important therefore, that TIG places emphasis on incentivising the submission of innovative algorithmic methods. TIG intends to do that by introducing a process for identifying algorithmic methods that may be eligible for higher rewards.
|
||||
|
||||
- To reflect the difference in impact and scale that improvements in algorithmic methods can have compared with code optimisations, TIG seeks to offer higher rewards for innovation in algorithmic methods. These higher rewards, so called “**Breakthrough Rewards”**, are made available through both**; (i)** a proportion of rewards in each Round being available to reward innovative algorithmic methods; and **(ii)** the design of the protocol enabling the rewards for innovative algorithmic methods to persist for multiple Rounds where they continue to provide the basis of algorithm implementations adopted by Benchmarkers.
|
||||
|
||||
**Intellectual Property**
|
||||
|
||||
- In addition to the significant performance gains potentially available from innovative algorithmic methods, whilst not patentable per se, they can also, subject to certain criteria (including being applied to provide a technical effect), provide the basis for a patent application. If a patent is applied for and granted it will contribute to the value of the intellectual property that underpins the value of TIG tokens.
|
||||
|
||||
|
||||
## What is a Token Holder Vote ?
|
||||
|
||||
- TIG believes that the determination of whether an algorithmic method is eligible for Breakthrough Rewards should be done by a token weighted vote.
|
||||
|
||||
- Because the value of TIG tokens should correlate with the acquisition, by TIG, of innovative algorithmic methods and associated valuable intellectual property and because both of these are incentivised by the offer of Breakthrough Rewards, TIG believes that the interests of TIG token holders are very well aligned with the objective of correctly determining when eligibility for potential Breakthrough Rewards is appropriate.
|
||||
|
||||
- Only votes cast will be counted for the purposes of determining the result of the Token Holder Vote. Abstentions and uncast votes will not be counted.
|
||||
|
||||
- Delegation of token votes will not be enabled initially but may be introduced at some point in the future.
|
||||
|
||||
- If 50% or more of the votes cast vote affirmatively, then the subject algorithmic method will be eligible for potential Breakthrough Rewards from the beginning of the Round in which it is made available for benchmarking. Whether an algorithmic method with the potential to earn Breakthrough Rewards does earn those rewards will depend on adoption by Benchmarkers sufficient to reach the threshold for merger.
|
||||
|
||||
- Algorithmic methods which are the subject of the vote will be made public for review at the beginning of the Round commencing one Round after the Round in which the algorithmic method was submitted to TIG.
|
||||
|
||||
- Voting in each Token Holder Vote will be open for the Round commencing two Rounds after the Round in which the algorithmic method subject to the vote was submitted to TIG.
|
||||
|
||||
- The result of the vote will be final. There will be no appeals process.
|
||||
|
||||
## When does a Token Holder Vote occur ?
|
||||
|
||||
- A Token Holder Vote will be called by TIG each time an innovator requests a determination of whether their submitted algorithmic method is eligible for potential Breakthrough Rewards.
|
||||
|
||||
## Do you qualify to vote ?
|
||||
|
||||
- If you own a TIG token and subject that token to a lock for a minimum determined period, then you are entitled to exercise one vote with respect to that token in the Token Holder Vote. Your influence on the outcome of the vote will therefore be weighted according to how many TIG tokens you hold **and** lock when voting.
|
||||
|
||||
## How do I vote ?
|
||||
|
||||
- Please see the [Token Holder page](https://play.tig.foundation/token-holder) for details.
|
||||
|
||||
## What are you voting on ?
|
||||
|
||||
- **You are voting on whether you think the algorithmic method subject to review is eligible for potential Breakthrough Rewards.**
|
||||
|
||||
## What should you consider when exercising your vote ?
|
||||
|
||||
- TIG believes that, amongst other things, the value of the TIG token will be best enhanced by the capture of innovative algorithmic methods which are patentable (and therefore novel and inventive) according to respective patent laws in different jurisdictions.
|
||||
|
||||
- **How you vote is entirely up to you.**
|
||||
|
||||
- When voting; assuming you wish to enhance the value of the TIG tokens that you hold, it is important that you are equipped to recognise the kind of innovation that Breakthrough Rewards are intended to incentivise.
|
||||
|
||||
- Where an algorithmic method is submitted to the game embodied in a code implementation, first you should distinguish the algorithmic method embodied in the algorithm implementation from the way the algorithmic method is expressed in the code implementation. [***See Appendix 2***].
|
||||
|
||||
- Having identified the algorithmic method, you should then determine the following:
|
||||
|
||||
- Is the algorithmic method **novel** ? [***See Appendix 3***].
|
||||
|
||||
- Is the algorithmic method **innovative** (i.e. is the method not obvious to someone skilled in the art) ? [***See Appendix 3***].
|
||||
|
||||
- ` `Algorithmic methods are the proper subject of a Token Holder Vote, whilst code implementations are not.
|
||||
|
||||
- TIG anticipates that the contributor of the incumbent algorithmic method will advocate **against** the novelty and inventiveness of the challenger algorithmic method where appropriate, and the challenger will advocate **for** its novelty and inventiveness. We will establish a discord channel for each Token Holder Vote so that these arguments on each side can be put, examined, and challenged. This should provide a rich, case specific, body of knowledge to help with the assessment that you will be making.
|
||||
|
||||
- TIG accepts that not all TIG token holders will have the necessary skills or experience to make an informed decision on whether eligibility for potential Breakthrough Rewards is appropriate in every specific case, but we hope that sufficient people with the requisite skill, knowledge and experience will engage, advocate and vote in such a way that eligibility is appropriately awarded. In the future we may introduce delegation of votes so that token holders can delegate analysis to experts that they trust to vote in a way that maximises token value.
|
||||
|
||||
- Essentially, we expect a rational token holder when voting in the Token Holder Vote to be assessing the novelty and inventiveness of a contributed algorithmic method, and by so doing, its patentability. To maximise the likelihood that an innovative algorithmic method will be ratified by Token Holder Vote as eligible for potential Breakthrough Rewards, contributors seeking Breakthrough Rewards have been advised to disclose the following information so that it may be assessed by token holders:
|
||||
|
||||
- **Novelty:** Results of a prior art search to identify any existing disclosures that might affect the novelty of your algorithmic method.
|
||||
|
||||
- **Inventiveness:** Documentation supporting how the algorithmic method differs from existing solutions (i.e. is non-obvious) and any new technical effect it achieves.
|
||||
|
||||
- **Technical Effect:** Documentation supporting patentability showing how the algorithmic method offers the potential to provide the basis for significant technical advancements. Document how the algorithmic method might have **real world application** e.g. in fields such as computer security, medical imaging, autonomous systems. or data compression.
|
||||
|
||||
|
||||
## The implications of the outcome of the token holder vote for the TIG protocol
|
||||
|
||||
To further assist you in exercising your vote in an informed way, we have set out below some of the implications of affirming an algorithmic method as being eligible for potential Breakthrough Rewards.
|
||||
|
||||
### Impact on Innovator Incentives
|
||||
|
||||
- A voter may be tempted to default to voting affirmatively on each Token Holder Vote with the intent of making an algorithmic method eligible for Breakthrough Rewards to ensure that there is at least an option for TIG to capture intellectual property with respect to the algorithmic method. This might seem the safe option to ensure that TIG can capture innovation presented to it. But you should note that TIG has the right to apply for patents based on all algorithmic methods contributed to the game with an associated request for a Token Holder Vote regardless of the outcome of the vote.
|
||||
|
||||
You should endeavour to avoid affirming eligibility for potential Breakthrough Rewards for algorithmic methods that are not, novel and inventive because this can have serious consequences by prejudicing and stifling innovation that does satisfy those criteria.
|
||||
|
||||
An algorithmic method that is incorrectly made eligible for potential Breakthrough Rewards may, if sufficiently adopted by benchmarkers, either; **(i)** deprive an innovator of their continuing Breakthrough Rewards thus disincentivising future innovation from that innovator and other observers; or **(ii)** deprive the TIG Treasury of bootstrap rewards (see below). Bootstrap Rewards should be used to fund/incentivise research towards an algorithmic breakthrough corresponding to challenges featured in TIG.
|
||||
|
||||
### Bootstrap Rewards
|
||||
|
||||
- It is possible, particularly in the early phases of TIG, that certain challenges will **not** be addressed in the Game by algorithmic methods that are eligible for Breakthrough Rewards because the algorithmic methods lack novelty or inventiveness or because no relevant algorithmic method has reached the threshold to be merged. In this situation those TIG token rewards ordinarily available for innovators as Breakthrough Rewards will be unallocated (we call these unallocated rewards, “**Bootstrap Rewards**”).
|
||||
|
||||
- Bootstrap Rewards will be allocated to the TIG Treasury. Bootstrap Rewards will be “ringfenced” and used very specifically by TIG (through distribution of bounty rewards) to incentivise the development of algorithmic methods which address challenges for which there are not presently algorithmic methods in the TIG Game which are eligible for potential Breakthrough Rewards or not merged. This should help algorithmic methods in the Game reach state of the art more quickly and this will accelerate the capture of meaningful value by TIG. This form of “centralised” allocation should only persist for as long as there are challenges in the game which are not solved by algorithmic methods that are earning Breakthrough Rewards.
|
||||
|
||||
## Intellectual Property
|
||||
|
||||
- TIG will only have the rights to file patent applications based on algorithmic methods where the contributor has sought Breakthrough Rewards by making a request for a Token Holder Vote. Innovators will only request Token Holder Votes if they have reasonable confidence that the process is fair and consistent allowing them to assess their chances of success in the vote with reasonable certainty.
|
||||
|
||||
## Commercial Value
|
||||
|
||||
- Algorithmic methods have the greatest potential to create the most value for TIG and any decisions should seek to ensure that innovators are sufficiently incentivised to submit them. Inappropriately voting against eligibility of an algorithmic method in a Token Holder Vote may cause valuable innovation to be lost to TIG.
|
||||
|
||||
|
||||
|
||||
# Appendix 1
|
||||
|
||||
**Comparison of Algorithmic Methods and Code Optimisations**
|
||||
|
||||
## 1. Order of Magnitude Differences (Big-O Complexity)
|
||||
|
||||
- **Algorithmic Improvement**: An improved algorithm can shift the complexity class of a solution, significantly impacting runtime, especially as input sizes grow. For instance, switching from a quadratic algorithm O(n2)O(n2)to a linear one O(n)O(n) can make a previously impractical solution feasible for large datasets.
|
||||
|
||||
- **Example**: Mergesort (O(nlogn)O(nlogn)) vs. Bubble Sort (O(n2)O(n2)) for sorting large lists. A dataset of 1,000,000 items might take Mergesort milliseconds, while Bubble Sort would take hours or even days.
|
||||
|
||||
- **Indicator:** A useful indicator of whether the underlying algorithmic method of an algorithm implementation has changed will be to test for any change in the efficiency of the implementation (where efficiency means the number of solutions per nonce). If the number of solutions per nonce has changed that will be a sufficient condition to conclude that there has been a change in the underlying algorithmic method. Note however that it is not always the case that a change to the underlying algorithm will result in a change in the number of solutions per nonce and so caution should be exercised not to reject an algorithmic method based on this indicator alone.
|
||||
|
||||
- **Code Optimization**: Within a given complexity class, optimizations yield speedups by improving constant factors or reducing overhead, often providing 10-50% performance gains rather than orders of magnitude.
|
||||
|
||||
- **Example**: Optimizing Mergesort by using in-place operations or efficient memory management can make the same O(nlogn)O(nlogn) algorithm faster but without changing its overall complexity.
|
||||
|
||||
## 2. Scalability for Large Datasets
|
||||
|
||||
- **Algorithmic Improvement**: Algorithmic advancements typically show exponential scalability. For instance, switching from a brute-force search (O(2n)O(2n)) to a dynamic programming solution (O(n2)O(n2)) for certain problems can enable the handling of very large datasets that were previously infeasible.
|
||||
|
||||
- **Example**: A naive brute-force solution for the traveling salesperson problem (TSP) takes O(n!)O(n!), but an approximation algorithm can reduce it to polynomial time, making the problem solvable for hundreds of cities rather than just a few dozen.
|
||||
|
||||
- **Code Optimization**: While code optimizations improve runtime, they rarely enable handling fundamentally larger datasets, as they don’t change the growth rate. They mostly improve efficiency for smaller to moderate-sized datasets.
|
||||
|
||||
- **Example**: Optimizing a brute-force TSP solution with efficient memoization and loop unrolling can improve performance by a constant factor but won’t make it feasible for large datasets, as the factorial growth remains.
|
||||
|
||||
## 3. Resource Efficiency (Memory, Power, etc.)
|
||||
|
||||
- **Algorithmic Improvement**: Changes in algorithms can drastically reduce memory usage, which is critical in constrained environments. For instance, algorithms that use **space-efficient data structures** (e.g., Bloom filters for probabilistic set membership) provide functionality with far lower memory requirements than traditional methods.
|
||||
|
||||
- **Example**: Dijkstra’s algorithm for shortest paths uses a priority queue, which reduces both time and memory usage compared to simpler graph traversal techniques.
|
||||
|
||||
- **Code Optimization**: Code optimizations help by refining memory usage through strategies like data locality, loop unrolling, and inlining functions. Gains tend to be moderate but can add up when processing high-frequency data or embedded systems.
|
||||
|
||||
- **Example**: Optimizing Dijkstra’s algorithm by reducing redundant allocations and using in-place updates can yield a noticeable speedup but won’t reduce memory asymptotically.
|
||||
|
||||
## 4. Impact of Problem Complexity on Efficiency Gains
|
||||
|
||||
- **Algorithmic Improvement**: The more complex the problem, the larger the potential gain from algorithmic innovation. NP-hard or NP-complete problems benefit greatly from efficient approximation algorithms or heuristics that deliver "good enough" solutions in polynomial time instead of exponential time.
|
||||
- **Example**: An approximation algorithm for the knapsack problem can yield near-optimal solutions in polynomial time, making it usable for large-scale decision-making tasks that brute-force methods could never handle.
|
||||
|
||||
- **Code Optimization**: For simpler problems, code optimizations are more effective because there is often less room for improvement with algorithm changes. Optimizations can be substantial in domains like graphics or signal processing where real-time performance is critical, and the underlying algorithms are already optimized.
|
||||
|
||||
- **Example**: In video processing, optimizations like SIMD (single instruction, multiple data) processing can achieve high-speed frame rendering, though the basic algorithms (e.g., filtering, transformation) may stay constant.
|
||||
|
||||
## Summary Comparison Table
|
||||
|
||||
|**Aspect**|**Algorithmic Improvement**|**Code Optimization**|
|
||||
| :- | :- | :- |
|
||||
|**Impact on Big-O Complexity**|Can reduce asymptotic complexity (e.g., O(n2)→O(nlogn)O(n2)→O(nlogn))|Doesn’t change Big-O; improves constant factors|
|
||||
|**Scalability**|Enables handling much larger datasets or complex problems|Limited scalability improvements; helps with small to moderate datasets|
|
||||
|**Typical Performance Gain**|Orders of magnitude (e.g., 10x to 1000x speedups)|Typically 10-50% improvement, up to 2-5x in extreme cases|
|
||||
|**Applicability**|Effective for complex or large-scale problems|Effective for refining already chosen algorithms|
|
||||
|**Resource Efficiency**|Reduces memory, power, or processing needs at a fundamental level|Reduces resource consumption at implementation level|
|
||||
|
||||
## Real-World Example: Sorting Large Datasets
|
||||
|
||||
- **Algorithmic Method**: Switching from a sorting algorithm like Bubble Sort (O(n2)O(n2)) to QuickSort (O(nlogn)O(nlogn)) can reduce runtime from hours to seconds for large datasets, demonstrating order-of-magnitude improvement.
|
||||
|
||||
- **Code Optimization**: Within QuickSort, optimizations like pivot selection improvements or reducing recursion depth provide further speed-ups but at a smaller scale (often 10-20% faster).
|
||||
|
||||
|
||||
|
||||
# Appendix 2
|
||||
|
||||
## Distinguishing between an Algorithmic Method and a Code Implementation
|
||||
|
||||
Algorithmic improvements offer substantial scalability and efficiency gains, especially for large or complex problems, by changing the approach to solving the problem. Code optimizations, on the other hand, fine-tune the implementation for moderate speed or resource improvements without fundamentally altering how the problem is addressed.
|
||||
|
||||
To distinguish between an algorithmic method and a code optimization of an implementation of an algorithmic method, it is helpful to focus on their purpose and level of abstraction:
|
||||
|
||||
## Algorithmic Method
|
||||
|
||||
- **Definition**: An algorithmic method is the fundamental approach or strategy for solving a problem, independent of specific code or language. It’s the "recipe" for how the problem is tackled.
|
||||
|
||||
- **Characteristics**:
|
||||
- **Conceptual Focus**: Concerned with the *steps* or *processes* required to solve the problem at a high level.
|
||||
|
||||
- **Algorithm Complexity**: Includes decisions about the algorithm's efficiency (e.g., using a divide-and-conquer algorithm rather than a brute-force approach).
|
||||
|
||||
- **Language-Agnostic**: The method itself is not bound to a particular programming language or code style.
|
||||
|
||||
- **Example**: Choosing between a binary search (O(log n)) and a linear search (O(n)) for searching in an array. This choice fundamentally changes the performance characteristics based on the algorithm.
|
||||
|
||||
## Code Optimization
|
||||
|
||||
- **Definition**: Code optimization involves making improvements to an existing implementation of an algorithm to enhance its efficiency, readability, or maintainability, without altering the core steps of the algorithm itself.
|
||||
|
||||
- **Characteristics**:
|
||||
- **Implementation Focus**: Works on refining the code that executes the algorithm, often targeting lower-level details like memory usage, computational overhead, or specific language/library features.
|
||||
|
||||
- **Micro-Efficiency**: Often about making the code faster or more efficient within the constraints of the chosen algorithm, such as reducing loop overhead, or minimizing function calls.
|
||||
|
||||
- **Language-Specific**: Tied to how the algorithm is implemented in code, considering the features and limitations of the language and runtime environment.
|
||||
|
||||
- **Example**: In a binary search implementation, an optimization could involve using bitwise shifts instead of division to calculate the midpoint, or unrolling loops to reduce function call overhead.
|
||||
|
||||
## Key Differences
|
||||
|
||||
|**Aspect**|**Algorithmic Method**|**Code Optimization**|
|
||||
| :- | :- | :- |
|
||||
|**Level of Abstraction**|High-level strategy|Low-level, specific to code and language|
|
||||
|**Goal**|Selects a method based on theoretical efficiency|Refines the chosen algorithm’s efficiency|
|
||||
|**Change in Complexity**|Alters the Big-O complexity (e.g., O(n) to O(log n))|Doesn't usually change Big-O complexity|
|
||||
|**Examples**|Choosing binary vs. linear search|Replacing division with shifts in midpoint calc|
|
||||
|
||||
You may consider automated ways of distinguishing between algorithmic methods and code optimisations, but this is challenging is due to the abstract nature of algorithmic methods versus the concrete nature of code optimizations. However, automated analysis can help, especially when focused on examining patterns in code structure, performance metrics, and complexity analysis. Here are some techniques and tools that could contribute to an automated distinction:
|
||||
|
||||
## Static Code Analysis for Complexity Analysis
|
||||
|
||||
- **Purpose**: This approach can identify algorithmic-level changes by analyzing code complexity.
|
||||
|
||||
- **Method**: Use tools to automatically calculate the time complexity of code snippets (e.g., Big-O Notation) based on control structures (loops, recursive calls) and data structures. Significant differences in complexity before and after changes often indicate an algorithmic method change.
|
||||
|
||||
- **Tools**: **Big-O Calculator** (like SymPy for theoretical analysis), **Cyclomatic Complexity Tools** (like SonarQube or Radon for Python). These tools can flag significant structural changes that may represent shifts in the underlying algorithm.
|
||||
|
||||
## Performance Profiling and Runtime Analysis
|
||||
|
||||
- **Purpose**: Automated profiling can reveal whether optimizations impact the entire algorithm or are local refinements.
|
||||
|
||||
- **Method**: Using profiling tools, track execution times and memory usage across different parts of the code. Look for specific functions or lines with changed CPU or memory usage patterns. Smaller, localized performance improvements are often code optimizations, while more significant performance shifts suggest algorithmic changes.
|
||||
|
||||
- **Tools**: **Profilers** (like Python’s cProfile, Py-Spy, or Intel VTune) can help monitor function-level runtime changes and memory usage, providing insights on where the optimizations are applied.
|
||||
|
||||
## Code Similarity and Structure Analysis
|
||||
|
||||
- **Purpose**: To identify structural changes in code that may imply a different algorithmic approach versus fine-tuning within the same structure.
|
||||
|
||||
- **Method**: Using abstract syntax trees (ASTs) or control flow graphs, compare the structure of two versions of code to detect major changes, such as different types of loops, recursion, or conditional branches. Major structural changes suggest an algorithmic shift, while minor adjustments usually indicate code optimization.
|
||||
|
||||
- **Tools**: **AST-based tools** (like Python’s ast module, Clang AST Matcher) to analyze differences in tree structures across code versions.
|
||||
|
||||
## Automated Benchmark Testing
|
||||
|
||||
- **Purpose**: Detect performance shifts at different input sizes to distinguish changes that affect complexity (algorithmic) versus fixed efficiency improvements (code optimization).
|
||||
|
||||
- **Method**: Set up a range of input sizes and measure the execution time and memory usage. Plot these results to identify the growth rate. A new algorithm will often show a different trend in growth rate, while a code optimization will display similar trends with only faster execution.
|
||||
|
||||
- **Tools**: **Benchmarking libraries** (like Google Benchmark for C++, pytest-benchmark for Python) to automate tests and capture metrics across different input sizes.
|
||||
|
||||
## Machine Learning for Pattern Recognition (Experimental)
|
||||
|
||||
- **Purpose**: Use machine learning to recognize patterns associated with algorithmic changes versus code optimizations based on historical examples.
|
||||
|
||||
- **Method**: Train models on labeled datasets of code differences, using features like complexity changes, structural edits, and performance metrics. Though still experimental, this approach could potentially identify trends that characterize algorithmic shifts versus optimizations.
|
||||
|
||||
- **Tools**: Machine learning libraries, such as **TensorFlow** or **scikit-learn**, can be trained to analyze code features if you have an adequately labeled dataset.
|
||||
|
||||
## Putting It All Together
|
||||
|
||||
An effective automated process would likely combine several of these methods:
|
||||
|
||||
1. **Complexity Analysis and Structure Comparison** to flag potential algorithm changes.
|
||||
|
||||
1. **Profiling and Benchmarking** to confirm performance changes and their scope.
|
||||
|
||||
1. **Pattern Recognition Models** to predict the nature of changes if trained models are available.
|
||||
|
||||
By layering these methods, an automated system could more accurately differentiate between high-level algorithmic changes and lower-level code optimizations.
|
||||
|
||||
An algorithmic method can be reasonably considered innovative over an existing method when it demonstrates clear, meaningful advancements in solving a particular problem, typically by improving performance, applicability, or problem-solving capabilities in ways that existing methods cannot.
|
||||
|
||||
|
||||
|
||||
# Appendix 3
|
||||
|
||||
## Novelty and Inventiveness
|
||||
|
||||
An algorithmic method is generally considered innovative when it either introduces a novel way to solve a problem or extends applicability or efficiency beyond prior methods**.**
|
||||
|
||||
- **Novelty:** The algorithm must be new and not previously disclosed in any prior art (publications, patents, or publicly accessible methods).
|
||||
|
||||
- **Application**: If an algorithm introduces a novel approach to solving a problem or achieves results in a way that has not been previously documented, it may meet this criterion.
|
||||
|
||||
- **Example**: A new method for efficiently compressing data using an original algorithm could be considered novel if it achieves results that other compression algorithms do not.
|
||||
|
||||
**Prior art** refers to any evidence that an invention or idea was already known before a particular date, in this case the date of submission of the algorithmic method to TIG. This includes previous patents, publications, products, demonstrations, or any public disclosures that describe similar technology, processes, or concepts.
|
||||
|
||||
## Practical Definition of Prior Art
|
||||
|
||||
**Prior Art**: Any publicly accessible information that describes the same or a similar invention, idea, or concept, which could indicate that the invention in question is not entirely new. Prior art can be anything publicly available in any form, including patents, journal articles, books, websites, products, and even conference presentations.
|
||||
|
||||
In simpler terms, if the core idea behind a new invention or concept is already documented in any format that others can access or view, it qualifies as prior art.
|
||||
|
||||
## How Prior Art is Used to Assess Innovation
|
||||
|
||||
Prior art plays a critical role in determining **whether an idea is truly innovative (or patentable) by helping assess two main criteria**:
|
||||
|
||||
1. **Novelty**: An invention must be novel, meaning it must differ in some meaningful way from what is already known. If prior art describes the invention’s main features, the invention is not considered novel
|
||||
|
||||
1. **Non-Obviousness**: Even if the invention isn’t exactly described in prior art, it must also be non-obvious, meaning it cannot be a trivial or obvious extension of existing ideas. The concept of "obviousness" is often subjective but generally means that someone skilled in the relevant field would not have easily thought of the invention. For example:
|
||||
|
||||
1. If an idea for a new app closely resembles an existing app but adds minor features anyone skilled in app design might think of, it may be deemed obvious.
|
||||
|
||||
## Practical Process to Determine Innovation Using Prior Art
|
||||
|
||||
1. **Search for Similar Inventions**: Perform prior art searches across patent databases, scientific publications, trade literature, and other sources.
|
||||
|
||||
1. **Analyze Differences**: Compare the proposed invention’s core claims against prior art. Look for any significant technical or functional distinctions.
|
||||
|
||||
1. **Assess Obviousness**: Determine if the invention would be an obvious adaptation of known solutions based on the prior art.
|
||||
|
||||
By systematically assessing whether an invention introduces new and non-obvious features beyond what prior art reveals, it’s possible to determine whether the idea qualifies as innovative.
|
||||
|
||||
## Prior art serves as the baseline against which inventiveness is evaluated
|
||||
|
||||
Here's how they relate to each other in detail:
|
||||
|
||||
**1. Prior Art Establishes the Known Landscape**
|
||||
|
||||
Prior art encompasses all known information in a particular field, including prior patents, published research, public demonstrations, and commercially available products. This body of knowledge sets the groundwork for assessing a new invention, essentially defining what is already known or has been achieved.
|
||||
|
||||
**2. Inventiveness Requires a Significant Advancement Over Prior Art**
|
||||
|
||||
For an invention to be considered **inventive** (or non-obvious), it must not only be new but also involve an element of ingenuity beyond what someone skilled in the field might naturally derive from prior art. The invention needs to solve a problem or offer a benefit in a way that isn’t apparent or trivial based on existing knowledge. In other words, if an invention closely resembles what is described in prior art, or could be easily derived from it by someone familiar with the field, it lacks inventiveness. However, if it introduces a novel approach or a surprising solution, it may meet the inventiveness criterion.
|
||||
|
||||
**3. Prior Art is Used to Determine Obviousness (Lack of Inventiveness)**
|
||||
|
||||
Prior art is used to assess whether an invention is **obvious**:
|
||||
|
||||
- **Direct Similarity**: If prior art describes an identical or nearly identical invention, the new invention is not novel or inventive.
|
||||
|
||||
- **Logical Extensions**: If the new invention is a predictable modification or extension of prior art, it may be deemed obvious. For example, changing the size, material, or shape of a known device without introducing a new function would likely be considered obvious.
|
||||
|
||||
- **Combination of Prior Art**: If the invention combines elements from different prior art sources in a straightforward way, it might also be considered obvious unless it leads to an unexpected or surprising result. For instance, combining two known mechanisms in a way that anyone in the field would naturally think of would likely be deemed an obvious combination of prior art.
|
||||
|
||||
**4. Inventiveness (Non-Obviousness) Often Requires a "Surprising" Element**
|
||||
|
||||
The more unexpected or surprising the solution provided by the invention, the stronger the case for inventiveness. If an invention introduces a solution that a person skilled in the art would not have thought to derive from the prior art, it is more likely to be deemed inventive.
|
||||
|
||||
## Summary of the Relationship
|
||||
|
||||
In short:
|
||||
|
||||
- **Prior art sets the reference point** against which a new invention’s inventiveness is measured.
|
||||
|
||||
- **Inventiveness** (non-obviousness) reflects how the invention goes beyond the known prior art in a non-trivial, unexpected way.
|
||||
|
||||
Thus, prior art is essential in judging inventiveness because it helps differentiate between truly innovative advancements and mere adaptations of existing knowledge.
|
||||
|
||||
An algorithmic method can be reasonably considered innovative over an existing method when it demonstrates clear, meaningful advancements in solving a particular problem, typically by improving performance, applicability, or problem-solving capabilities in ways that existing methods cannot.
|
||||
|
||||
Here are the key criteria for determining when an algorithmic method is genuinely innovative:
|
||||
|
||||
## Improved Asymptotic Complexity
|
||||
|
||||
- **Criteria**: The new algorithm shows a better Big-O complexity (e.g., improving from O(n2)O(n2) to O(nlogn)O(nlogn) or O(n)O(n)), especially in worst-case or average-case scenarios.
|
||||
|
||||
- **Example**: The development of QuickSort (average O(nlogn)O(nlogn)) offered an innovative improvement over Bubble Sort (O(n2)O(n2)) for sorting, making it more practical for larger datasets. Similarly, Dijkstra’s algorithm for shortest paths was later innovatively improved with A\* for specific scenarios.
|
||||
|
||||
## Significant Performance Gains on Practical Inputs
|
||||
|
||||
- **Criteria**: Even if the Big-O complexity is similar, the new method shows substantially faster performance or lower resource consumption on real-world datasets, often due to reduced constants, more efficient data handling, or better memory locality.
|
||||
|
||||
- **Example**: The Strassen algorithm for matrix multiplication has a better theoretical complexity than standard matrix multiplication. However, some algorithms, even without better complexity, may still be innovative if they perform more efficiently in real-world use cases.
|
||||
|
||||
## Extended Applicability to Broader Problem Classes
|
||||
|
||||
- **Criteria**: The new algorithm applies to a wider range of scenarios, input types, or constraints than previous methods, extending the range of problems that can be solved effectively.
|
||||
|
||||
- **Example**: The invention of dynamic programming extended the applicability of algorithms to optimization problems with overlapping subproblems, such as in the knapsack problem or sequence alignment, which weren’t solvable efficiently with previous methods.
|
||||
|
||||
## Enhanced Robustness and Practicality
|
||||
|
||||
- **Criteria**: The new algorithm handles edge cases or special cases (e.g., large-scale data, sparse data, highly variable data distributions) more gracefully than its predecessors, which makes it more robust for general use.
|
||||
|
||||
- **Example**: K-Means++ is an innovation over the standard K-Means clustering algorithm because it initializes centroids in a way that avoids poor clustering outcomes, especially on skewed data, enhancing robustness.
|
||||
|
||||
## New Approach or Paradigm
|
||||
|
||||
- **Criteria**: The algorithm introduces a fundamentally new approach, technique, or paradigm that shifts how a problem is conceptualized or solved, opening up new avenues for solutions.
|
||||
|
||||
- **Example**: The development of neural networks and backpropagation introduced a paradigm shift in machine learning, allowing problems that were previously considered infeasible to be solved with learning-based approaches.
|
||||
|
||||
## Improved Scalability or Feasibility for Large Data Sets
|
||||
|
||||
- **Criteria**: The algorithm is designed to be more scalable, specifically addressing issues like memory constraints, parallelization, or distributed computing.
|
||||
|
||||
- **Example**: MapReduce, by Google, introduced an innovative algorithmic method for parallel and distributed processing, making it feasible to process massive datasets in a way that standard methods couldn’t handle efficiently.
|
||||
|
||||
## Reduction of Resource Requirements (Memory, Power, etc.)
|
||||
|
||||
- **Criteria**: The new method substantially reduces resource requirements, such as memory usage, power consumption, or communication overhead, often by optimizing data structures or computational pathways.
|
||||
|
||||
- **Example**: Bloom filters are an innovative space-efficient probabilistic data structure that allows for quick set membership testing with minimal memory usage, which is valuable in constrained environments.
|
||||
|
||||
## Proof of Optimality or Approximation Guarantees
|
||||
|
||||
- **Criteria**: If an algorithm provides a proven optimal solution or a guaranteed approximation ratio for NP-hard or difficult problems, it represents a strong innovation.
|
||||
|
||||
- **Example**: Approximation algorithms for NP-hard problems, like the Traveling Salesman Problem (TSP), where algorithms can find solutions within a guaranteed percentage of the optimal, are considered innovative because they provide useful solutions where exact methods are impractical.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
extra/explo
BIN
extra/explo
Binary file not shown.
5
funding.json
Normal file
5
funding.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"opRetro": {
|
||||
"projectId": "0xb0898866c2c537c61b37d04916e3879ef78fed630181c1ffaf492499d174f0bf"
|
||||
}
|
||||
}
|
||||
1268
swagger.yaml
1268
swagger.yaml
File diff suppressed because it is too large
Load Diff
@ -20,10 +20,11 @@ WASM blobs for an algorithm are stored in the `wasm` subfolder and can be downlo
|
||||
|
||||
1. New submissions get their branch pushed to a private version of this repository
|
||||
2. CI will compile submissions into WASM
|
||||
3. A new submission made during round `X` will have its branch pushed to the public version of this repository at the start of round `X + 3`
|
||||
4. Once public, benchmarkers can use the algorithm for benchmarking
|
||||
5. Every block, algorithms with at least 25% adoption earn a merge point
|
||||
6. At the end of a round, a the algorithm from each challenge with the most merge points, meeting the minimum threshold of 5040, gets merged to the `main` branch
|
||||
3. A new submission made during round `X` will have its branch pushed to the public version of this repository at the start of round `X + 2`
|
||||
4. At the start of round `X + 4`, the submission becomes active, where benchmarkers can use the algorithm for benchmarking
|
||||
5. Every block, algorithms with at least 25% adoption earn rewards and a merge point
|
||||
6. At the end of a round, a algorithm from each challenge with the most merge points, meeting the minimum threshold of 5040, gets merged to the `main` branch
|
||||
* Merged algorithms receive rewards every block where their adoption is greater than 0%
|
||||
|
||||
# License
|
||||
|
||||
|
||||
110
tig-algorithms/src/knapsack/knapheudp/benchmarker_outbound.rs
Normal file
110
tig-algorithms/src/knapsack/knapheudp/benchmarker_outbound.rs
Normal file
@ -0,0 +1,110 @@
|
||||
/*!
|
||||
Copyright 2024 AllFather
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use tig_challenges::knapsack::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let max_weight = challenge.max_weight as usize;
|
||||
let min_value = challenge.min_value as usize;
|
||||
let num_items = challenge.difficulty.num_items;
|
||||
|
||||
let weights: Vec<usize> = challenge.weights.iter().map(|&w| w as usize).collect();
|
||||
let values: Vec<usize> = challenge.values.iter().map(|&v| v as usize).collect();
|
||||
|
||||
let mut sorted_items: Vec<(usize, f64)> = (0..num_items)
|
||||
.map(|i| (i, values[i] as f64 / weights[i] as f64))
|
||||
.collect();
|
||||
sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
let mut upper_bound = 0;
|
||||
let mut remaining_weight = max_weight;
|
||||
for &(item_index, ratio) in &sorted_items {
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
if item_weight <= remaining_weight {
|
||||
upper_bound += item_value;
|
||||
remaining_weight -= item_weight;
|
||||
} else {
|
||||
upper_bound += (ratio * remaining_weight as f64).floor() as usize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if upper_bound < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut dp = vec![0; max_weight + 1];
|
||||
let mut selected = vec![vec![false; max_weight + 1]; num_items];
|
||||
|
||||
for (i, &(item_index, _)) in sorted_items.iter().enumerate() {
|
||||
let weight = weights[item_index];
|
||||
let value = values[item_index];
|
||||
|
||||
for w in (weight..=max_weight).rev() {
|
||||
let new_value = dp[w - weight] + value;
|
||||
if new_value > dp[w] {
|
||||
dp[w] = new_value;
|
||||
selected[i][w] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if dp[max_weight] >= min_value {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if dp[max_weight] < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut items = Vec::new();
|
||||
let mut w = max_weight;
|
||||
for i in (0..num_items).rev() {
|
||||
if selected[i][w] {
|
||||
let item_index = sorted_items[i].0;
|
||||
items.push(item_index);
|
||||
w -= weights[item_index];
|
||||
}
|
||||
if w == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Solution { items }))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
110
tig-algorithms/src/knapsack/knapheudp/commercial.rs
Normal file
110
tig-algorithms/src/knapsack/knapheudp/commercial.rs
Normal file
@ -0,0 +1,110 @@
|
||||
/*!
|
||||
Copyright 2024 AllFather
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use tig_challenges::knapsack::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let max_weight = challenge.max_weight as usize;
|
||||
let min_value = challenge.min_value as usize;
|
||||
let num_items = challenge.difficulty.num_items;
|
||||
|
||||
let weights: Vec<usize> = challenge.weights.iter().map(|&w| w as usize).collect();
|
||||
let values: Vec<usize> = challenge.values.iter().map(|&v| v as usize).collect();
|
||||
|
||||
let mut sorted_items: Vec<(usize, f64)> = (0..num_items)
|
||||
.map(|i| (i, values[i] as f64 / weights[i] as f64))
|
||||
.collect();
|
||||
sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
let mut upper_bound = 0;
|
||||
let mut remaining_weight = max_weight;
|
||||
for &(item_index, ratio) in &sorted_items {
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
if item_weight <= remaining_weight {
|
||||
upper_bound += item_value;
|
||||
remaining_weight -= item_weight;
|
||||
} else {
|
||||
upper_bound += (ratio * remaining_weight as f64).floor() as usize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if upper_bound < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut dp = vec![0; max_weight + 1];
|
||||
let mut selected = vec![vec![false; max_weight + 1]; num_items];
|
||||
|
||||
for (i, &(item_index, _)) in sorted_items.iter().enumerate() {
|
||||
let weight = weights[item_index];
|
||||
let value = values[item_index];
|
||||
|
||||
for w in (weight..=max_weight).rev() {
|
||||
let new_value = dp[w - weight] + value;
|
||||
if new_value > dp[w] {
|
||||
dp[w] = new_value;
|
||||
selected[i][w] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if dp[max_weight] >= min_value {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if dp[max_weight] < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut items = Vec::new();
|
||||
let mut w = max_weight;
|
||||
for i in (0..num_items).rev() {
|
||||
if selected[i][w] {
|
||||
let item_index = sorted_items[i].0;
|
||||
items.push(item_index);
|
||||
w -= weights[item_index];
|
||||
}
|
||||
if w == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Solution { items }))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
110
tig-algorithms/src/knapsack/knapheudp/inbound.rs
Normal file
110
tig-algorithms/src/knapsack/knapheudp/inbound.rs
Normal file
@ -0,0 +1,110 @@
|
||||
/*!
|
||||
Copyright 2024 AllFather
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use tig_challenges::knapsack::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let max_weight = challenge.max_weight as usize;
|
||||
let min_value = challenge.min_value as usize;
|
||||
let num_items = challenge.difficulty.num_items;
|
||||
|
||||
let weights: Vec<usize> = challenge.weights.iter().map(|&w| w as usize).collect();
|
||||
let values: Vec<usize> = challenge.values.iter().map(|&v| v as usize).collect();
|
||||
|
||||
let mut sorted_items: Vec<(usize, f64)> = (0..num_items)
|
||||
.map(|i| (i, values[i] as f64 / weights[i] as f64))
|
||||
.collect();
|
||||
sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
let mut upper_bound = 0;
|
||||
let mut remaining_weight = max_weight;
|
||||
for &(item_index, ratio) in &sorted_items {
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
if item_weight <= remaining_weight {
|
||||
upper_bound += item_value;
|
||||
remaining_weight -= item_weight;
|
||||
} else {
|
||||
upper_bound += (ratio * remaining_weight as f64).floor() as usize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if upper_bound < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut dp = vec![0; max_weight + 1];
|
||||
let mut selected = vec![vec![false; max_weight + 1]; num_items];
|
||||
|
||||
for (i, &(item_index, _)) in sorted_items.iter().enumerate() {
|
||||
let weight = weights[item_index];
|
||||
let value = values[item_index];
|
||||
|
||||
for w in (weight..=max_weight).rev() {
|
||||
let new_value = dp[w - weight] + value;
|
||||
if new_value > dp[w] {
|
||||
dp[w] = new_value;
|
||||
selected[i][w] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if dp[max_weight] >= min_value {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if dp[max_weight] < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut items = Vec::new();
|
||||
let mut w = max_weight;
|
||||
for i in (0..num_items).rev() {
|
||||
if selected[i][w] {
|
||||
let item_index = sorted_items[i].0;
|
||||
items.push(item_index);
|
||||
w -= weights[item_index];
|
||||
}
|
||||
if w == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Solution { items }))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
110
tig-algorithms/src/knapsack/knapheudp/innovator_outbound.rs
Normal file
110
tig-algorithms/src/knapsack/knapheudp/innovator_outbound.rs
Normal file
@ -0,0 +1,110 @@
|
||||
/*!
|
||||
Copyright 2024 AllFather
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use tig_challenges::knapsack::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let max_weight = challenge.max_weight as usize;
|
||||
let min_value = challenge.min_value as usize;
|
||||
let num_items = challenge.difficulty.num_items;
|
||||
|
||||
let weights: Vec<usize> = challenge.weights.iter().map(|&w| w as usize).collect();
|
||||
let values: Vec<usize> = challenge.values.iter().map(|&v| v as usize).collect();
|
||||
|
||||
let mut sorted_items: Vec<(usize, f64)> = (0..num_items)
|
||||
.map(|i| (i, values[i] as f64 / weights[i] as f64))
|
||||
.collect();
|
||||
sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
let mut upper_bound = 0;
|
||||
let mut remaining_weight = max_weight;
|
||||
for &(item_index, ratio) in &sorted_items {
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
if item_weight <= remaining_weight {
|
||||
upper_bound += item_value;
|
||||
remaining_weight -= item_weight;
|
||||
} else {
|
||||
upper_bound += (ratio * remaining_weight as f64).floor() as usize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if upper_bound < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut dp = vec![0; max_weight + 1];
|
||||
let mut selected = vec![vec![false; max_weight + 1]; num_items];
|
||||
|
||||
for (i, &(item_index, _)) in sorted_items.iter().enumerate() {
|
||||
let weight = weights[item_index];
|
||||
let value = values[item_index];
|
||||
|
||||
for w in (weight..=max_weight).rev() {
|
||||
let new_value = dp[w - weight] + value;
|
||||
if new_value > dp[w] {
|
||||
dp[w] = new_value;
|
||||
selected[i][w] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if dp[max_weight] >= min_value {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if dp[max_weight] < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut items = Vec::new();
|
||||
let mut w = max_weight;
|
||||
for i in (0..num_items).rev() {
|
||||
if selected[i][w] {
|
||||
let item_index = sorted_items[i].0;
|
||||
items.push(item_index);
|
||||
w -= weights[item_index];
|
||||
}
|
||||
if w == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Solution { items }))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
110
tig-algorithms/src/knapsack/knapheudp/open_data.rs
Normal file
110
tig-algorithms/src/knapsack/knapheudp/open_data.rs
Normal file
@ -0,0 +1,110 @@
|
||||
/*!
|
||||
Copyright 2024 AllFather
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use tig_challenges::knapsack::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let max_weight = challenge.max_weight as usize;
|
||||
let min_value = challenge.min_value as usize;
|
||||
let num_items = challenge.difficulty.num_items;
|
||||
|
||||
let weights: Vec<usize> = challenge.weights.iter().map(|&w| w as usize).collect();
|
||||
let values: Vec<usize> = challenge.values.iter().map(|&v| v as usize).collect();
|
||||
|
||||
let mut sorted_items: Vec<(usize, f64)> = (0..num_items)
|
||||
.map(|i| (i, values[i] as f64 / weights[i] as f64))
|
||||
.collect();
|
||||
sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
let mut upper_bound = 0;
|
||||
let mut remaining_weight = max_weight;
|
||||
for &(item_index, ratio) in &sorted_items {
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
if item_weight <= remaining_weight {
|
||||
upper_bound += item_value;
|
||||
remaining_weight -= item_weight;
|
||||
} else {
|
||||
upper_bound += (ratio * remaining_weight as f64).floor() as usize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if upper_bound < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut dp = vec![0; max_weight + 1];
|
||||
let mut selected = vec![vec![false; max_weight + 1]; num_items];
|
||||
|
||||
for (i, &(item_index, _)) in sorted_items.iter().enumerate() {
|
||||
let weight = weights[item_index];
|
||||
let value = values[item_index];
|
||||
|
||||
for w in (weight..=max_weight).rev() {
|
||||
let new_value = dp[w - weight] + value;
|
||||
if new_value > dp[w] {
|
||||
dp[w] = new_value;
|
||||
selected[i][w] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if dp[max_weight] >= min_value {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if dp[max_weight] < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut items = Vec::new();
|
||||
let mut w = max_weight;
|
||||
for i in (0..num_items).rev() {
|
||||
if selected[i][w] {
|
||||
let item_index = sorted_items[i].0;
|
||||
items.push(item_index);
|
||||
w -= weights[item_index];
|
||||
}
|
||||
if w == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Solution { items }))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
115
tig-algorithms/src/knapsack/knapmaxxing/benchmarker_outbound.rs
Normal file
115
tig-algorithms/src/knapsack/knapmaxxing/benchmarker_outbound.rs
Normal file
@ -0,0 +1,115 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use tig_challenges::knapsack::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let max_weight = challenge.max_weight as usize;
|
||||
let min_value = challenge.min_value as usize;
|
||||
let num_items = challenge.difficulty.num_items;
|
||||
|
||||
let max_weight_plus_one = max_weight + 1;
|
||||
|
||||
let weights: Vec<usize> = challenge.weights.iter().map(|weight| *weight as usize).collect();
|
||||
let values: Vec<usize> = challenge.values.iter().map(|value| *value as usize).collect();
|
||||
|
||||
let mut sorted_items: Vec<(usize, f64)> = (0..num_items)
|
||||
.map(|i| (i, values[i] as f64 / weights[i] as f64))
|
||||
.collect();
|
||||
sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
let mut upper_bound = 0;
|
||||
let mut remaining_weight = max_weight;
|
||||
for &(item_index, ratio) in &sorted_items {
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
if item_weight <= remaining_weight {
|
||||
upper_bound += item_value;
|
||||
remaining_weight -= item_weight;
|
||||
} else {
|
||||
upper_bound += (ratio * remaining_weight as f64).floor() as usize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if upper_bound < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_states = (num_items + 1) * (max_weight_plus_one);
|
||||
let mut dp = vec![0; num_states];
|
||||
|
||||
for i in 1..=num_items {
|
||||
let (item_index, _) = sorted_items[i - 1];
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
let i_minus_one_times_max_weight_plus_one = (i - 1) * max_weight_plus_one;
|
||||
let i_times_max_weight_plus_one = i * max_weight_plus_one;
|
||||
for w in (item_weight..=max_weight).rev() {
|
||||
let prev_state = i_minus_one_times_max_weight_plus_one + w;
|
||||
let curr_state = i_times_max_weight_plus_one + w;
|
||||
dp[curr_state] = dp[prev_state].max(dp[prev_state - item_weight] + item_value);
|
||||
}
|
||||
}
|
||||
|
||||
let mut items = Vec::with_capacity(num_items);
|
||||
let mut i = num_items;
|
||||
let mut w = max_weight;
|
||||
let mut total_value = 0;
|
||||
while i > 0 && total_value < min_value {
|
||||
let (item_index, _) = sorted_items[i - 1];
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
let prev_state = (i - 1) * (max_weight_plus_one) + w;
|
||||
let curr_state = i * (max_weight_plus_one) + w;
|
||||
if dp[curr_state] != dp[prev_state] {
|
||||
items.push(item_index);
|
||||
w -= item_weight;
|
||||
total_value += item_value;
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
if total_value >= min_value {
|
||||
Ok(Some(Solution { items }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
115
tig-algorithms/src/knapsack/knapmaxxing/commercial.rs
Normal file
115
tig-algorithms/src/knapsack/knapmaxxing/commercial.rs
Normal file
@ -0,0 +1,115 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use tig_challenges::knapsack::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let max_weight = challenge.max_weight as usize;
|
||||
let min_value = challenge.min_value as usize;
|
||||
let num_items = challenge.difficulty.num_items;
|
||||
|
||||
let max_weight_plus_one = max_weight + 1;
|
||||
|
||||
let weights: Vec<usize> = challenge.weights.iter().map(|weight| *weight as usize).collect();
|
||||
let values: Vec<usize> = challenge.values.iter().map(|value| *value as usize).collect();
|
||||
|
||||
let mut sorted_items: Vec<(usize, f64)> = (0..num_items)
|
||||
.map(|i| (i, values[i] as f64 / weights[i] as f64))
|
||||
.collect();
|
||||
sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
let mut upper_bound = 0;
|
||||
let mut remaining_weight = max_weight;
|
||||
for &(item_index, ratio) in &sorted_items {
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
if item_weight <= remaining_weight {
|
||||
upper_bound += item_value;
|
||||
remaining_weight -= item_weight;
|
||||
} else {
|
||||
upper_bound += (ratio * remaining_weight as f64).floor() as usize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if upper_bound < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_states = (num_items + 1) * (max_weight_plus_one);
|
||||
let mut dp = vec![0; num_states];
|
||||
|
||||
for i in 1..=num_items {
|
||||
let (item_index, _) = sorted_items[i - 1];
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
let i_minus_one_times_max_weight_plus_one = (i - 1) * max_weight_plus_one;
|
||||
let i_times_max_weight_plus_one = i * max_weight_plus_one;
|
||||
for w in (item_weight..=max_weight).rev() {
|
||||
let prev_state = i_minus_one_times_max_weight_plus_one + w;
|
||||
let curr_state = i_times_max_weight_plus_one + w;
|
||||
dp[curr_state] = dp[prev_state].max(dp[prev_state - item_weight] + item_value);
|
||||
}
|
||||
}
|
||||
|
||||
let mut items = Vec::with_capacity(num_items);
|
||||
let mut i = num_items;
|
||||
let mut w = max_weight;
|
||||
let mut total_value = 0;
|
||||
while i > 0 && total_value < min_value {
|
||||
let (item_index, _) = sorted_items[i - 1];
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
let prev_state = (i - 1) * (max_weight_plus_one) + w;
|
||||
let curr_state = i * (max_weight_plus_one) + w;
|
||||
if dp[curr_state] != dp[prev_state] {
|
||||
items.push(item_index);
|
||||
w -= item_weight;
|
||||
total_value += item_value;
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
if total_value >= min_value {
|
||||
Ok(Some(Solution { items }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
115
tig-algorithms/src/knapsack/knapmaxxing/inbound.rs
Normal file
115
tig-algorithms/src/knapsack/knapmaxxing/inbound.rs
Normal file
@ -0,0 +1,115 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use tig_challenges::knapsack::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let max_weight = challenge.max_weight as usize;
|
||||
let min_value = challenge.min_value as usize;
|
||||
let num_items = challenge.difficulty.num_items;
|
||||
|
||||
let max_weight_plus_one = max_weight + 1;
|
||||
|
||||
let weights: Vec<usize> = challenge.weights.iter().map(|weight| *weight as usize).collect();
|
||||
let values: Vec<usize> = challenge.values.iter().map(|value| *value as usize).collect();
|
||||
|
||||
let mut sorted_items: Vec<(usize, f64)> = (0..num_items)
|
||||
.map(|i| (i, values[i] as f64 / weights[i] as f64))
|
||||
.collect();
|
||||
sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
let mut upper_bound = 0;
|
||||
let mut remaining_weight = max_weight;
|
||||
for &(item_index, ratio) in &sorted_items {
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
if item_weight <= remaining_weight {
|
||||
upper_bound += item_value;
|
||||
remaining_weight -= item_weight;
|
||||
} else {
|
||||
upper_bound += (ratio * remaining_weight as f64).floor() as usize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if upper_bound < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_states = (num_items + 1) * (max_weight_plus_one);
|
||||
let mut dp = vec![0; num_states];
|
||||
|
||||
for i in 1..=num_items {
|
||||
let (item_index, _) = sorted_items[i - 1];
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
let i_minus_one_times_max_weight_plus_one = (i - 1) * max_weight_plus_one;
|
||||
let i_times_max_weight_plus_one = i * max_weight_plus_one;
|
||||
for w in (item_weight..=max_weight).rev() {
|
||||
let prev_state = i_minus_one_times_max_weight_plus_one + w;
|
||||
let curr_state = i_times_max_weight_plus_one + w;
|
||||
dp[curr_state] = dp[prev_state].max(dp[prev_state - item_weight] + item_value);
|
||||
}
|
||||
}
|
||||
|
||||
let mut items = Vec::with_capacity(num_items);
|
||||
let mut i = num_items;
|
||||
let mut w = max_weight;
|
||||
let mut total_value = 0;
|
||||
while i > 0 && total_value < min_value {
|
||||
let (item_index, _) = sorted_items[i - 1];
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
let prev_state = (i - 1) * (max_weight_plus_one) + w;
|
||||
let curr_state = i * (max_weight_plus_one) + w;
|
||||
if dp[curr_state] != dp[prev_state] {
|
||||
items.push(item_index);
|
||||
w -= item_weight;
|
||||
total_value += item_value;
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
if total_value >= min_value {
|
||||
Ok(Some(Solution { items }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
115
tig-algorithms/src/knapsack/knapmaxxing/innovator_outbound.rs
Normal file
115
tig-algorithms/src/knapsack/knapmaxxing/innovator_outbound.rs
Normal file
@ -0,0 +1,115 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use tig_challenges::knapsack::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let max_weight = challenge.max_weight as usize;
|
||||
let min_value = challenge.min_value as usize;
|
||||
let num_items = challenge.difficulty.num_items;
|
||||
|
||||
let max_weight_plus_one = max_weight + 1;
|
||||
|
||||
let weights: Vec<usize> = challenge.weights.iter().map(|weight| *weight as usize).collect();
|
||||
let values: Vec<usize> = challenge.values.iter().map(|value| *value as usize).collect();
|
||||
|
||||
let mut sorted_items: Vec<(usize, f64)> = (0..num_items)
|
||||
.map(|i| (i, values[i] as f64 / weights[i] as f64))
|
||||
.collect();
|
||||
sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
let mut upper_bound = 0;
|
||||
let mut remaining_weight = max_weight;
|
||||
for &(item_index, ratio) in &sorted_items {
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
if item_weight <= remaining_weight {
|
||||
upper_bound += item_value;
|
||||
remaining_weight -= item_weight;
|
||||
} else {
|
||||
upper_bound += (ratio * remaining_weight as f64).floor() as usize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if upper_bound < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_states = (num_items + 1) * (max_weight_plus_one);
|
||||
let mut dp = vec![0; num_states];
|
||||
|
||||
for i in 1..=num_items {
|
||||
let (item_index, _) = sorted_items[i - 1];
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
let i_minus_one_times_max_weight_plus_one = (i - 1) * max_weight_plus_one;
|
||||
let i_times_max_weight_plus_one = i * max_weight_plus_one;
|
||||
for w in (item_weight..=max_weight).rev() {
|
||||
let prev_state = i_minus_one_times_max_weight_plus_one + w;
|
||||
let curr_state = i_times_max_weight_plus_one + w;
|
||||
dp[curr_state] = dp[prev_state].max(dp[prev_state - item_weight] + item_value);
|
||||
}
|
||||
}
|
||||
|
||||
let mut items = Vec::with_capacity(num_items);
|
||||
let mut i = num_items;
|
||||
let mut w = max_weight;
|
||||
let mut total_value = 0;
|
||||
while i > 0 && total_value < min_value {
|
||||
let (item_index, _) = sorted_items[i - 1];
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
let prev_state = (i - 1) * (max_weight_plus_one) + w;
|
||||
let curr_state = i * (max_weight_plus_one) + w;
|
||||
if dp[curr_state] != dp[prev_state] {
|
||||
items.push(item_index);
|
||||
w -= item_weight;
|
||||
total_value += item_value;
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
if total_value >= min_value {
|
||||
Ok(Some(Solution { items }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
4
tig-algorithms/src/knapsack/knapmaxxing/mod.rs
Normal file
4
tig-algorithms/src/knapsack/knapmaxxing/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod benchmarker_outbound;
|
||||
pub use benchmarker_outbound::solve_challenge;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL};
|
||||
115
tig-algorithms/src/knapsack/knapmaxxing/open_data.rs
Normal file
115
tig-algorithms/src/knapsack/knapmaxxing/open_data.rs
Normal file
@ -0,0 +1,115 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use tig_challenges::knapsack::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let max_weight = challenge.max_weight as usize;
|
||||
let min_value = challenge.min_value as usize;
|
||||
let num_items = challenge.difficulty.num_items;
|
||||
|
||||
let max_weight_plus_one = max_weight + 1;
|
||||
|
||||
let weights: Vec<usize> = challenge.weights.iter().map(|weight| *weight as usize).collect();
|
||||
let values: Vec<usize> = challenge.values.iter().map(|value| *value as usize).collect();
|
||||
|
||||
let mut sorted_items: Vec<(usize, f64)> = (0..num_items)
|
||||
.map(|i| (i, values[i] as f64 / weights[i] as f64))
|
||||
.collect();
|
||||
sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
let mut upper_bound = 0;
|
||||
let mut remaining_weight = max_weight;
|
||||
for &(item_index, ratio) in &sorted_items {
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
if item_weight <= remaining_weight {
|
||||
upper_bound += item_value;
|
||||
remaining_weight -= item_weight;
|
||||
} else {
|
||||
upper_bound += (ratio * remaining_weight as f64).floor() as usize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if upper_bound < min_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_states = (num_items + 1) * (max_weight_plus_one);
|
||||
let mut dp = vec![0; num_states];
|
||||
|
||||
for i in 1..=num_items {
|
||||
let (item_index, _) = sorted_items[i - 1];
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
let i_minus_one_times_max_weight_plus_one = (i - 1) * max_weight_plus_one;
|
||||
let i_times_max_weight_plus_one = i * max_weight_plus_one;
|
||||
for w in (item_weight..=max_weight).rev() {
|
||||
let prev_state = i_minus_one_times_max_weight_plus_one + w;
|
||||
let curr_state = i_times_max_weight_plus_one + w;
|
||||
dp[curr_state] = dp[prev_state].max(dp[prev_state - item_weight] + item_value);
|
||||
}
|
||||
}
|
||||
|
||||
let mut items = Vec::with_capacity(num_items);
|
||||
let mut i = num_items;
|
||||
let mut w = max_weight;
|
||||
let mut total_value = 0;
|
||||
while i > 0 && total_value < min_value {
|
||||
let (item_index, _) = sorted_items[i - 1];
|
||||
let item_weight = weights[item_index];
|
||||
let item_value = values[item_index];
|
||||
|
||||
let prev_state = (i - 1) * (max_weight_plus_one) + w;
|
||||
let curr_state = i * (max_weight_plus_one) + w;
|
||||
if dp[curr_state] != dp[prev_state] {
|
||||
items.push(item_index);
|
||||
w -= item_weight;
|
||||
total_value += item_value;
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
if total_value >= min_value {
|
||||
Ok(Some(Solution { items }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -11,7 +11,8 @@ pub use dynamic as c003_a001;
|
||||
|
||||
// c003_a006
|
||||
|
||||
// c003_a007
|
||||
pub mod knapmaxxing;
|
||||
pub use knapmaxxing as c003_a007;
|
||||
|
||||
// c003_a008
|
||||
|
||||
@ -35,7 +36,8 @@ pub use dynamic as c003_a001;
|
||||
|
||||
// c003_a018
|
||||
|
||||
// c003_a019
|
||||
pub mod knapheudp;
|
||||
pub use knapheudp as c003_a019;
|
||||
|
||||
// c003_a020
|
||||
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
/*!
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright [year copyright work created] [name of copyright owner]
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
Identity of Submitter [name of person or entity that submits the Work to TIG]
|
||||
|
||||
UAI [UAI (if applicable)]
|
||||
|
||||
Licensed under the TIG Inbound Game License v2.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
@ -13,6 +17,24 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// REMOVE BELOW SECTION IF UNUSED
|
||||
/*
|
||||
REFERENCES AND ACKNOWLEDGMENTS
|
||||
|
||||
This implementation is based on or inspired by existing work. Citations and
|
||||
acknowledgments below:
|
||||
|
||||
1. Academic Papers:
|
||||
- [Author(s), "Paper Title", DOI (if available)]
|
||||
|
||||
2. Code References:
|
||||
- [Author(s), URL]
|
||||
|
||||
3. Other:
|
||||
- [Author(s), Details]
|
||||
|
||||
*/
|
||||
|
||||
// TIG's UI uses the pattern `tig_challenges::<challenge_name>` to automatically detect your algorithm's challenge
|
||||
use anyhow::{anyhow, Result};
|
||||
use tig_challenges::knapsack::{Challenge, Solution};
|
||||
|
||||
@ -0,0 +1,250 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashSet;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual = HashSet::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual.insert(i);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(&i) = residual.iter().next() {
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..c.len())];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
};
|
||||
|
||||
for &c in &n_clauses[v] {
|
||||
if variables[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.remove(&c);
|
||||
}
|
||||
} else {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.insert(c);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if variables[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.insert(c);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
} else {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.remove(&c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
250
tig-algorithms/src/satisfiability/fast_walk_sat/commercial.rs
Normal file
250
tig-algorithms/src/satisfiability/fast_walk_sat/commercial.rs
Normal file
@ -0,0 +1,250 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashSet;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual = HashSet::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual.insert(i);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(&i) = residual.iter().next() {
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..c.len())];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
};
|
||||
|
||||
for &c in &n_clauses[v] {
|
||||
if variables[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.remove(&c);
|
||||
}
|
||||
} else {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.insert(c);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if variables[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.insert(c);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
} else {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.remove(&c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
250
tig-algorithms/src/satisfiability/fast_walk_sat/inbound.rs
Normal file
250
tig-algorithms/src/satisfiability/fast_walk_sat/inbound.rs
Normal file
@ -0,0 +1,250 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashSet;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual = HashSet::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual.insert(i);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(&i) = residual.iter().next() {
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..c.len())];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
};
|
||||
|
||||
for &c in &n_clauses[v] {
|
||||
if variables[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.remove(&c);
|
||||
}
|
||||
} else {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.insert(c);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if variables[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.insert(c);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
} else {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.remove(&c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,250 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashSet;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual = HashSet::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual.insert(i);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(&i) = residual.iter().next() {
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..c.len())];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
};
|
||||
|
||||
for &c in &n_clauses[v] {
|
||||
if variables[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.remove(&c);
|
||||
}
|
||||
} else {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.insert(c);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if variables[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.insert(c);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
} else {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.remove(&c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
4
tig-algorithms/src/satisfiability/fast_walk_sat/mod.rs
Normal file
4
tig-algorithms/src/satisfiability/fast_walk_sat/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod benchmarker_outbound;
|
||||
pub use benchmarker_outbound::solve_challenge;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL};
|
||||
250
tig-algorithms/src/satisfiability/fast_walk_sat/open_data.rs
Normal file
250
tig-algorithms/src/satisfiability/fast_walk_sat/open_data.rs
Normal file
@ -0,0 +1,250 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashSet;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual = HashSet::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual.insert(i);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(&i) = residual.iter().next() {
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..c.len())];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
};
|
||||
|
||||
for &c in &n_clauses[v] {
|
||||
if variables[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.remove(&c);
|
||||
}
|
||||
} else {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.insert(c);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if variables[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.insert(c);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
} else {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual.remove(&c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,267 @@
|
||||
/*!
|
||||
Copyright 2024 Clifford Algueraz
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
267
tig-algorithms/src/satisfiability/inbound/commercial.rs
Normal file
267
tig-algorithms/src/satisfiability/inbound/commercial.rs
Normal file
@ -0,0 +1,267 @@
|
||||
/*!
|
||||
Copyright 2024 Clifford Algueraz
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
267
tig-algorithms/src/satisfiability/inbound/inbound.rs
Normal file
267
tig-algorithms/src/satisfiability/inbound/inbound.rs
Normal file
@ -0,0 +1,267 @@
|
||||
/*!
|
||||
Copyright 2024 Clifford Algueraz
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
267
tig-algorithms/src/satisfiability/inbound/innovator_outbound.rs
Normal file
267
tig-algorithms/src/satisfiability/inbound/innovator_outbound.rs
Normal file
@ -0,0 +1,267 @@
|
||||
/*!
|
||||
Copyright 2024 Clifford Algueraz
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
4
tig-algorithms/src/satisfiability/inbound/mod.rs
Normal file
4
tig-algorithms/src/satisfiability/inbound/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod benchmarker_outbound;
|
||||
pub use benchmarker_outbound::solve_challenge;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL};
|
||||
267
tig-algorithms/src/satisfiability/inbound/open_data.rs
Normal file
267
tig-algorithms/src/satisfiability/inbound/open_data.rs
Normal file
@ -0,0 +1,267 @@
|
||||
/*!
|
||||
Copyright 2024 Clifford Algueraz
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -7,7 +7,8 @@ pub use schnoing as c001_a001;
|
||||
|
||||
// c001_a004
|
||||
|
||||
// c001_a005
|
||||
pub mod walk_sat;
|
||||
pub use walk_sat as c001_a005;
|
||||
|
||||
// c001_a006
|
||||
|
||||
@ -19,9 +20,11 @@ pub use schnoing as c001_a001;
|
||||
|
||||
// c001_a010
|
||||
|
||||
// c001_a011
|
||||
pub mod fast_walk_sat;
|
||||
pub use fast_walk_sat as c001_a011;
|
||||
|
||||
// c001_a012
|
||||
pub mod sprint_sat;
|
||||
pub use sprint_sat as c001_a012;
|
||||
|
||||
// c001_a013
|
||||
|
||||
@ -33,7 +36,8 @@ pub use schnoing as c001_a001;
|
||||
|
||||
// c001_a017
|
||||
|
||||
// c001_a018
|
||||
pub mod inbound;
|
||||
pub use inbound as c001_a018;
|
||||
|
||||
// c001_a019
|
||||
|
||||
@ -43,7 +47,8 @@ pub use schnoing as c001_a001;
|
||||
|
||||
// c001_a022
|
||||
|
||||
// c001_a023
|
||||
pub mod sat_allocd;
|
||||
pub use sat_allocd as c001_a023;
|
||||
|
||||
// c001_a024
|
||||
|
||||
@ -59,13 +64,15 @@ pub use schnoing as c001_a001;
|
||||
|
||||
// c001_a030
|
||||
|
||||
// c001_a031
|
||||
pub mod sat_optima;
|
||||
pub use sat_optima as c001_a031;
|
||||
|
||||
// c001_a032
|
||||
|
||||
// c001_a033
|
||||
|
||||
// c001_a034
|
||||
pub mod sat_global;
|
||||
pub use sat_global as c001_a034;
|
||||
|
||||
// c001_a035
|
||||
|
||||
|
||||
@ -0,0 +1,284 @@
|
||||
/*!
|
||||
Copyright 2024 AllFather
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad.clear();
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= num_variables * 35 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
284
tig-algorithms/src/satisfiability/sat_allocd/commercial.rs
Normal file
284
tig-algorithms/src/satisfiability/sat_allocd/commercial.rs
Normal file
@ -0,0 +1,284 @@
|
||||
/*!
|
||||
Copyright 2024 AllFather
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad.clear();
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= num_variables * 35 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
284
tig-algorithms/src/satisfiability/sat_allocd/inbound.rs
Normal file
284
tig-algorithms/src/satisfiability/sat_allocd/inbound.rs
Normal file
@ -0,0 +1,284 @@
|
||||
/*!
|
||||
Copyright 2024 AllFather
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad.clear();
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= num_variables * 35 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,284 @@
|
||||
/*!
|
||||
Copyright 2024 AllFather
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad.clear();
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= num_variables * 35 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
4
tig-algorithms/src/satisfiability/sat_allocd/mod.rs
Normal file
4
tig-algorithms/src/satisfiability/sat_allocd/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod benchmarker_outbound;
|
||||
pub use benchmarker_outbound::solve_challenge;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL};
|
||||
284
tig-algorithms/src/satisfiability/sat_allocd/open_data.rs
Normal file
284
tig-algorithms/src/satisfiability/sat_allocd/open_data.rs
Normal file
@ -0,0 +1,284 @@
|
||||
/*!
|
||||
Copyright 2024 AllFather
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad.clear();
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= num_variables * 35 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,297 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
let num_p = p_clauses[v].len();
|
||||
let num_n = n_clauses[v].len();
|
||||
|
||||
let vad = num_p as f32 / (num_p + num_n).max(1) as f32;
|
||||
|
||||
if vad >= 1.8 {
|
||||
variables[v] = true;
|
||||
} else if vad <= 0.56 {
|
||||
variables[v] = false;
|
||||
} else {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut num_good_so_far: Vec<u8> = vec![0; num_clauses];
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 && variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
} else if l < 0 && !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64;
|
||||
let num_vars = challenge.difficulty.num_variables as f64;
|
||||
let max_fuel = 2000000000.0;
|
||||
let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars;
|
||||
let flip_fuel = 900.0 + 1.8 * clauses_ratio;
|
||||
let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize;
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
|
||||
let rand_val = rng.gen::<usize>();
|
||||
|
||||
let i = residual_[rand_val % residual_.len()];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = usize::MAX;
|
||||
let c = &mut clauses[i];
|
||||
|
||||
if c.len() > 1 {
|
||||
let random_index = rand_val % c.len();
|
||||
c.swap(0, random_index);
|
||||
}
|
||||
for &l in c.iter() {
|
||||
let abs_l = l.abs() as usize - 1;
|
||||
let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] };
|
||||
|
||||
let mut sad = 0;
|
||||
for &c in clauses_to_check {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = abs_l;
|
||||
}
|
||||
}
|
||||
|
||||
let v = if min_sad == 0 {
|
||||
v_min_sad
|
||||
} else if rng.gen_bool(0.5) {
|
||||
c[0].abs() as usize - 1
|
||||
} else {
|
||||
v_min_sad
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= max_num_rounds {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
297
tig-algorithms/src/satisfiability/sat_global/commercial.rs
Normal file
297
tig-algorithms/src/satisfiability/sat_global/commercial.rs
Normal file
@ -0,0 +1,297 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
let num_p = p_clauses[v].len();
|
||||
let num_n = n_clauses[v].len();
|
||||
|
||||
let vad = num_p as f32 / (num_p + num_n).max(1) as f32;
|
||||
|
||||
if vad >= 1.8 {
|
||||
variables[v] = true;
|
||||
} else if vad <= 0.56 {
|
||||
variables[v] = false;
|
||||
} else {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut num_good_so_far: Vec<u8> = vec![0; num_clauses];
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 && variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
} else if l < 0 && !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64;
|
||||
let num_vars = challenge.difficulty.num_variables as f64;
|
||||
let max_fuel = 2000000000.0;
|
||||
let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars;
|
||||
let flip_fuel = 900.0 + 1.8 * clauses_ratio;
|
||||
let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize;
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
|
||||
let rand_val = rng.gen::<usize>();
|
||||
|
||||
let i = residual_[rand_val % residual_.len()];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = usize::MAX;
|
||||
let c = &mut clauses[i];
|
||||
|
||||
if c.len() > 1 {
|
||||
let random_index = rand_val % c.len();
|
||||
c.swap(0, random_index);
|
||||
}
|
||||
for &l in c.iter() {
|
||||
let abs_l = l.abs() as usize - 1;
|
||||
let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] };
|
||||
|
||||
let mut sad = 0;
|
||||
for &c in clauses_to_check {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = abs_l;
|
||||
}
|
||||
}
|
||||
|
||||
let v = if min_sad == 0 {
|
||||
v_min_sad
|
||||
} else if rng.gen_bool(0.5) {
|
||||
c[0].abs() as usize - 1
|
||||
} else {
|
||||
v_min_sad
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= max_num_rounds {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
297
tig-algorithms/src/satisfiability/sat_global/inbound.rs
Normal file
297
tig-algorithms/src/satisfiability/sat_global/inbound.rs
Normal file
@ -0,0 +1,297 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
let num_p = p_clauses[v].len();
|
||||
let num_n = n_clauses[v].len();
|
||||
|
||||
let vad = num_p as f32 / (num_p + num_n).max(1) as f32;
|
||||
|
||||
if vad >= 1.8 {
|
||||
variables[v] = true;
|
||||
} else if vad <= 0.56 {
|
||||
variables[v] = false;
|
||||
} else {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut num_good_so_far: Vec<u8> = vec![0; num_clauses];
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 && variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
} else if l < 0 && !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64;
|
||||
let num_vars = challenge.difficulty.num_variables as f64;
|
||||
let max_fuel = 2000000000.0;
|
||||
let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars;
|
||||
let flip_fuel = 900.0 + 1.8 * clauses_ratio;
|
||||
let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize;
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
|
||||
let rand_val = rng.gen::<usize>();
|
||||
|
||||
let i = residual_[rand_val % residual_.len()];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = usize::MAX;
|
||||
let c = &mut clauses[i];
|
||||
|
||||
if c.len() > 1 {
|
||||
let random_index = rand_val % c.len();
|
||||
c.swap(0, random_index);
|
||||
}
|
||||
for &l in c.iter() {
|
||||
let abs_l = l.abs() as usize - 1;
|
||||
let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] };
|
||||
|
||||
let mut sad = 0;
|
||||
for &c in clauses_to_check {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = abs_l;
|
||||
}
|
||||
}
|
||||
|
||||
let v = if min_sad == 0 {
|
||||
v_min_sad
|
||||
} else if rng.gen_bool(0.5) {
|
||||
c[0].abs() as usize - 1
|
||||
} else {
|
||||
v_min_sad
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= max_num_rounds {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,297 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
let num_p = p_clauses[v].len();
|
||||
let num_n = n_clauses[v].len();
|
||||
|
||||
let vad = num_p as f32 / (num_p + num_n).max(1) as f32;
|
||||
|
||||
if vad >= 1.8 {
|
||||
variables[v] = true;
|
||||
} else if vad <= 0.56 {
|
||||
variables[v] = false;
|
||||
} else {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut num_good_so_far: Vec<u8> = vec![0; num_clauses];
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 && variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
} else if l < 0 && !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64;
|
||||
let num_vars = challenge.difficulty.num_variables as f64;
|
||||
let max_fuel = 2000000000.0;
|
||||
let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars;
|
||||
let flip_fuel = 900.0 + 1.8 * clauses_ratio;
|
||||
let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize;
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
|
||||
let rand_val = rng.gen::<usize>();
|
||||
|
||||
let i = residual_[rand_val % residual_.len()];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = usize::MAX;
|
||||
let c = &mut clauses[i];
|
||||
|
||||
if c.len() > 1 {
|
||||
let random_index = rand_val % c.len();
|
||||
c.swap(0, random_index);
|
||||
}
|
||||
for &l in c.iter() {
|
||||
let abs_l = l.abs() as usize - 1;
|
||||
let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] };
|
||||
|
||||
let mut sad = 0;
|
||||
for &c in clauses_to_check {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = abs_l;
|
||||
}
|
||||
}
|
||||
|
||||
let v = if min_sad == 0 {
|
||||
v_min_sad
|
||||
} else if rng.gen_bool(0.5) {
|
||||
c[0].abs() as usize - 1
|
||||
} else {
|
||||
v_min_sad
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= max_num_rounds {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
4
tig-algorithms/src/satisfiability/sat_global/mod.rs
Normal file
4
tig-algorithms/src/satisfiability/sat_global/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod benchmarker_outbound;
|
||||
pub use benchmarker_outbound::solve_challenge;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL};
|
||||
297
tig-algorithms/src/satisfiability/sat_global/open_data.rs
Normal file
297
tig-algorithms/src/satisfiability/sat_global/open_data.rs
Normal file
@ -0,0 +1,297 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
let num_p = p_clauses[v].len();
|
||||
let num_n = n_clauses[v].len();
|
||||
|
||||
let vad = num_p as f32 / (num_p + num_n).max(1) as f32;
|
||||
|
||||
if vad >= 1.8 {
|
||||
variables[v] = true;
|
||||
} else if vad <= 0.56 {
|
||||
variables[v] = false;
|
||||
} else {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut num_good_so_far: Vec<u8> = vec![0; num_clauses];
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 && variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
} else if l < 0 && !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64;
|
||||
let num_vars = challenge.difficulty.num_variables as f64;
|
||||
let max_fuel = 2000000000.0;
|
||||
let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars;
|
||||
let flip_fuel = 900.0 + 1.8 * clauses_ratio;
|
||||
let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize;
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
|
||||
let rand_val = rng.gen::<usize>();
|
||||
|
||||
let i = residual_[rand_val % residual_.len()];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = usize::MAX;
|
||||
let c = &mut clauses[i];
|
||||
|
||||
if c.len() > 1 {
|
||||
let random_index = rand_val % c.len();
|
||||
c.swap(0, random_index);
|
||||
}
|
||||
for &l in c.iter() {
|
||||
let abs_l = l.abs() as usize - 1;
|
||||
let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] };
|
||||
|
||||
let mut sad = 0;
|
||||
for &c in clauses_to_check {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = abs_l;
|
||||
}
|
||||
}
|
||||
|
||||
let v = if min_sad == 0 {
|
||||
v_min_sad
|
||||
} else if rng.gen_bool(0.5) {
|
||||
c[0].abs() as usize - 1
|
||||
} else {
|
||||
v_min_sad
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= max_num_rounds {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,285 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
|
||||
let i = residual_[rng.gen_range(0..residual_.len())];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad.clear();
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= num_variables * 35 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
285
tig-algorithms/src/satisfiability/sat_optima/commercial.rs
Normal file
285
tig-algorithms/src/satisfiability/sat_optima/commercial.rs
Normal file
@ -0,0 +1,285 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
|
||||
let i = residual_[rng.gen_range(0..residual_.len())];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad.clear();
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= num_variables * 35 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
285
tig-algorithms/src/satisfiability/sat_optima/inbound.rs
Normal file
285
tig-algorithms/src/satisfiability/sat_optima/inbound.rs
Normal file
@ -0,0 +1,285 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
|
||||
let i = residual_[rng.gen_range(0..residual_.len())];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad.clear();
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= num_variables * 35 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,285 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
|
||||
let i = residual_[rng.gen_range(0..residual_.len())];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad.clear();
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= num_variables * 35 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
4
tig-algorithms/src/satisfiability/sat_optima/mod.rs
Normal file
4
tig-algorithms/src/satisfiability/sat_optima/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod benchmarker_outbound;
|
||||
pub use benchmarker_outbound::solve_challenge;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL};
|
||||
285
tig-algorithms/src/satisfiability/sat_optima/open_data.rs
Normal file
285
tig-algorithms/src/satisfiability/sat_optima/open_data.rs
Normal file
@ -0,0 +1,285 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut rounds = 0;
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len()); // Preallocate with capacity
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![Vec::new(); num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
// Preallocate capacity for p_clauses and n_clauses
|
||||
for c in &clauses {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
if p_clauses[var].capacity() == 0 {
|
||||
p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
} else {
|
||||
if n_clauses[var].capacity() == 0 {
|
||||
n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if !residual_.is_empty() {
|
||||
|
||||
let i = residual_[rng.gen_range(0..residual_.len())];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad.clear();
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..(c.len() as u32)) as usize];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
rounds += 1;
|
||||
if rounds >= num_variables * 35 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -17,7 +17,7 @@ use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::from_seed(challenge.seed);
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let mut variables: Vec<bool> = (0..num_variables).map(|_| rng.gen::<bool>()).collect();
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::from_seed(challenge.seed);
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let mut variables: Vec<bool> = (0..num_variables).map(|_| rng.gen::<bool>()).collect();
|
||||
|
||||
|
||||
156
tig-algorithms/src/satisfiability/schnoing/cuda_example.rs
Normal file
156
tig-algorithms/src/satisfiability/schnoing/cuda_example.rs
Normal file
@ -0,0 +1,156 @@
|
||||
/*!
|
||||
Copyright 2024 TIG Foundation
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(challenge.seeds[0] as u64);
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let mut variables: Vec<bool> = (0..num_variables).map(|_| rng.gen::<bool>()).collect();
|
||||
|
||||
// Pre-generate a bunch of random integers
|
||||
// IMPORTANT! When generating random numbers, never use usize! usize bytes varies from system to system
|
||||
let rand_ints: Vec<usize> = (0..2 * num_variables)
|
||||
.map(|_| rng.gen_range(0..1_000_000_000u32) as usize)
|
||||
.collect();
|
||||
|
||||
for i in 0..num_variables {
|
||||
// Evaluate clauses and find any that are unsatisfied
|
||||
let substituted: Vec<bool> = challenge
|
||||
.clauses
|
||||
.iter()
|
||||
.map(|clause| {
|
||||
clause.iter().any(|&literal| {
|
||||
let var_idx = literal.abs() as usize - 1;
|
||||
let var_value = variables[var_idx];
|
||||
(literal > 0 && var_value) || (literal < 0 && !var_value)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let unsatisfied_clauses: Vec<usize> = substituted
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, &satisfied)| if !satisfied { Some(idx) } else { None })
|
||||
.collect();
|
||||
|
||||
let num_unsatisfied_clauses = unsatisfied_clauses.len();
|
||||
if num_unsatisfied_clauses == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
// Flip the value of a random variable from a random unsatisfied clause
|
||||
let rand_unsatisfied_clause_idx = rand_ints[2 * i] % num_unsatisfied_clauses;
|
||||
let rand_unsatisfied_clause = unsatisfied_clauses[rand_unsatisfied_clause_idx];
|
||||
let rand_variable_idx = rand_ints[2 * i + 1] % 3;
|
||||
let rand_variable =
|
||||
challenge.clauses[rand_unsatisfied_clause][rand_variable_idx].abs() as usize - 1;
|
||||
variables[rand_variable] = !variables[rand_variable];
|
||||
}
|
||||
|
||||
Ok(Some(Solution { variables }))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = Some(CudaKernel {
|
||||
// Example CUDA code from https://github.com/coreylowman/cudarc/blob/main/examples/matmul-kernel.rs
|
||||
src: r#"
|
||||
extern "C" __global__ void matmul(float* A, float* B, float* C, int N) {
|
||||
int ROW = blockIdx.y*blockDim.y+threadIdx.y;
|
||||
int COL = blockIdx.x*blockDim.x+threadIdx.x;
|
||||
|
||||
float tmpSum = 0;
|
||||
|
||||
if (ROW < N && COL < N) {
|
||||
// each thread computes one element of the block sub-matrix
|
||||
for (int i = 0; i < N; i++) {
|
||||
tmpSum += A[ROW * N + i] * B[i * N + COL];
|
||||
}
|
||||
}
|
||||
C[ROW * N + COL] = tmpSum;
|
||||
}
|
||||
"#,
|
||||
funcs: &["matmul"],
|
||||
});
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
// Example CUDA code from https://github.com/coreylowman/cudarc/blob/main/examples/matmul-kernel.rs
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let a_host = [1.0f32, 2.0, 3.0, 4.0];
|
||||
let b_host = [1.0f32, 2.0, 3.0, 4.0];
|
||||
let mut c_host = [0.0f32; 4];
|
||||
|
||||
let a_dev = dev.htod_sync_copy(&a_host)?;
|
||||
let b_dev = dev.htod_sync_copy(&b_host)?;
|
||||
let mut c_dev = dev.htod_sync_copy(&c_host)?;
|
||||
|
||||
println!("Copied in {:?}", start.elapsed());
|
||||
|
||||
let cfg = LaunchConfig {
|
||||
block_dim: (2, 2, 1),
|
||||
grid_dim: (1, 1, 1),
|
||||
shared_mem_bytes: 0,
|
||||
};
|
||||
unsafe {
|
||||
funcs
|
||||
.remove("matmul")
|
||||
.unwrap()
|
||||
.launch(cfg, (&a_dev, &b_dev, &mut c_dev, 2i32))
|
||||
}?;
|
||||
|
||||
dev.dtoh_sync_copy_into(&c_dev, &mut c_host)?;
|
||||
println!("Found {:?} in {:?}", c_host, start.elapsed());
|
||||
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -17,7 +17,7 @@ use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::from_seed(challenge.seed);
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let mut variables: Vec<bool> = (0..num_variables).map(|_| rng.gen::<bool>()).collect();
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::from_seed(challenge.seed);
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let mut variables: Vec<bool> = (0..num_variables).map(|_| rng.gen::<bool>()).collect();
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::from_seed(challenge.seed);
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let mut variables: Vec<bool> = (0..num_variables).map(|_| rng.gen::<bool>()).collect();
|
||||
|
||||
|
||||
@ -0,0 +1,267 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..c.len())];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
267
tig-algorithms/src/satisfiability/sprint_sat/commercial.rs
Normal file
267
tig-algorithms/src/satisfiability/sprint_sat/commercial.rs
Normal file
@ -0,0 +1,267 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..c.len())];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
267
tig-algorithms/src/satisfiability/sprint_sat/inbound.rs
Normal file
267
tig-algorithms/src/satisfiability/sprint_sat/inbound.rs
Normal file
@ -0,0 +1,267 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..c.len())];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,267 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..c.len())];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
4
tig-algorithms/src/satisfiability/sprint_sat/mod.rs
Normal file
4
tig-algorithms/src/satisfiability/sprint_sat/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod benchmarker_outbound;
|
||||
pub use benchmarker_outbound::solve_challenge;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL};
|
||||
267
tig-algorithms/src/satisfiability/sprint_sat/open_data.rs
Normal file
267
tig-algorithms/src/satisfiability/sprint_sat/open_data.rs
Normal file
@ -0,0 +1,267 @@
|
||||
/*!
|
||||
Copyright 2024 Dominic Kennedy
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
|
||||
let mut p_single = vec![false; challenge.difficulty.num_variables];
|
||||
let mut n_single = vec![false; challenge.difficulty.num_variables];
|
||||
|
||||
let mut clauses_ = challenge.clauses.clone();
|
||||
let mut clauses: Vec<Vec<i32>> = Vec::with_capacity(clauses_.len());
|
||||
|
||||
let mut dead = false;
|
||||
|
||||
while !(dead) {
|
||||
let mut done = true;
|
||||
for c in &clauses_ {
|
||||
let mut c_: Vec<i32> = Vec::with_capacity(c.len());
|
||||
let mut skip = false;
|
||||
for (i, l) in c.iter().enumerate() {
|
||||
if (p_single[(l.abs() - 1) as usize] && *l > 0)
|
||||
|| (n_single[(l.abs() - 1) as usize] && *l < 0)
|
||||
|| c[(i + 1)..].contains(&-l)
|
||||
{
|
||||
skip = true;
|
||||
break;
|
||||
} else if p_single[(l.abs() - 1) as usize]
|
||||
|| n_single[(l.abs() - 1) as usize]
|
||||
|| c[(i + 1)..].contains(&l)
|
||||
{
|
||||
done = false;
|
||||
continue;
|
||||
} else {
|
||||
c_.push(*l);
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
done = false;
|
||||
continue;
|
||||
};
|
||||
match c_[..] {
|
||||
[l] => {
|
||||
done = false;
|
||||
if l > 0 {
|
||||
if n_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
p_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
} else {
|
||||
if p_single[(l.abs() - 1) as usize] {
|
||||
dead = true;
|
||||
break;
|
||||
} else {
|
||||
n_single[(l.abs() - 1) as usize] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[] => {
|
||||
dead = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
clauses.push(c_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
} else {
|
||||
clauses_ = clauses;
|
||||
clauses = Vec::with_capacity(clauses_.len());
|
||||
}
|
||||
}
|
||||
|
||||
if dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let num_clauses = clauses.len();
|
||||
|
||||
let mut p_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
let mut n_clauses: Vec<Vec<usize>> = vec![vec![]; num_variables];
|
||||
|
||||
let mut variables = vec![false; num_variables];
|
||||
for v in 0..num_variables {
|
||||
if p_single[v] {
|
||||
variables[v] = true
|
||||
} else if n_single[v] {
|
||||
variables[v] = false
|
||||
} else {
|
||||
variables[v] = rng.gen_bool(0.5)
|
||||
}
|
||||
}
|
||||
let mut num_good_so_far: Vec<usize> = vec![0; num_clauses];
|
||||
|
||||
for (i, &ref c) in clauses.iter().enumerate() {
|
||||
for &l in c {
|
||||
let var = (l.abs() - 1) as usize;
|
||||
if l > 0 {
|
||||
p_clauses[var].push(i);
|
||||
if variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
} else {
|
||||
n_clauses[var].push(i);
|
||||
if !variables[var] {
|
||||
num_good_so_far[i] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut residual_ = Vec::with_capacity(num_clauses);
|
||||
let mut residual_indices = HashMap::with_capacity(num_clauses);
|
||||
|
||||
for (i, &num_good) in num_good_so_far.iter().enumerate() {
|
||||
if num_good == 0 {
|
||||
residual_.push(i);
|
||||
residual_indices.insert(i, residual_.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if attempts >= num_variables * 25 {
|
||||
return Ok(None);
|
||||
}
|
||||
if !residual_.is_empty() {
|
||||
let i = residual_[0];
|
||||
let mut min_sad = clauses.len();
|
||||
let mut v_min_sad = vec![];
|
||||
let c = &clauses[i];
|
||||
for &l in c {
|
||||
let mut sad = 0 as usize;
|
||||
if variables[(l.abs() - 1) as usize] {
|
||||
for &c in &p_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[(l.abs() - 1) as usize] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
sad += 1;
|
||||
if sad > min_sad {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sad < min_sad {
|
||||
min_sad = sad;
|
||||
v_min_sad = vec![(l.abs() - 1) as usize];
|
||||
} else if sad == min_sad {
|
||||
v_min_sad.push((l.abs() - 1) as usize);
|
||||
}
|
||||
}
|
||||
let v = if min_sad == 0 {
|
||||
if v_min_sad.len() == 1 {
|
||||
v_min_sad[0]
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
} else {
|
||||
if rng.gen_bool(0.5) {
|
||||
let l = c[rng.gen_range(0..c.len())];
|
||||
(l.abs() - 1) as usize
|
||||
} else {
|
||||
v_min_sad[rng.gen_range(0..v_min_sad.len())]
|
||||
}
|
||||
};
|
||||
|
||||
if variables[v] {
|
||||
for &c in &n_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &c in &p_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
} else {
|
||||
for &c in &n_clauses[v] {
|
||||
if num_good_so_far[c] == 1 {
|
||||
residual_.push(c);
|
||||
residual_indices.insert(c, residual_.len() - 1);
|
||||
}
|
||||
num_good_so_far[c] -= 1;
|
||||
}
|
||||
|
||||
for &c in &p_clauses[v] {
|
||||
num_good_so_far[c] += 1;
|
||||
if num_good_so_far[c] == 1 {
|
||||
let i = residual_indices.remove(&c).unwrap();
|
||||
let last = residual_.pop().unwrap();
|
||||
if i < residual_.len() {
|
||||
residual_[i] = last;
|
||||
residual_indices.insert(last, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables[v] = !variables[v];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -1,7 +1,11 @@
|
||||
/*!
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright [year copyright work created] [name of copyright owner]
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
Identity of Submitter [name of person or entity that submits the Work to TIG]
|
||||
|
||||
UAI [UAI (if applicable)]
|
||||
|
||||
Licensed under the TIG Inbound Game License v2.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
@ -13,6 +17,24 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// REMOVE BELOW SECTION IF UNUSED
|
||||
/*
|
||||
REFERENCES AND ACKNOWLEDGMENTS
|
||||
|
||||
This implementation is based on or inspired by existing work. Citations and
|
||||
acknowledgments below:
|
||||
|
||||
1. Academic Papers:
|
||||
- [Author(s), "Paper Title", DOI (if available)]
|
||||
|
||||
2. Code References:
|
||||
- [Author(s), URL]
|
||||
|
||||
3. Other:
|
||||
- [Author(s), Details]
|
||||
|
||||
*/
|
||||
|
||||
// TIG's UI uses the pattern `tig_challenges::<challenge_name>` to automatically detect your algorithm's challenge
|
||||
use anyhow::{anyhow, Result};
|
||||
use tig_challenges::satisfiability::{Challenge, Solution};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*!
|
||||
Copyright 2024 Test
|
||||
Copyright 2024 Chad Blanchard
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
@ -14,28 +14,44 @@ language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// TIG's UI uses the pattern `tig_challenges::<challenge_name>` to automatically detect your algorithm's challenge
|
||||
use anyhow::Result;
|
||||
use tig_challenges::vector_search::{euclidean_distance, Challenge, Solution};
|
||||
use rand::prelude::*;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> Result<Option<Solution>> {
|
||||
let mut indexes = Vec::<usize>::new();
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut found = false;
|
||||
for (idx, v) in challenge.vector_database.iter().enumerate() {
|
||||
if euclidean_distance(query, v) <= challenge.max_distance {
|
||||
indexes.push(idx);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return Ok(None);
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let max_flips = 1000;
|
||||
|
||||
let mut variables: Vec<bool> = (0..num_variables).map(|_| rng.gen::<bool>()).collect();
|
||||
|
||||
for _ in 0..max_flips {
|
||||
let mut unsatisfied_clauses: Vec<&Vec<i32>> = challenge
|
||||
.clauses
|
||||
.iter()
|
||||
.filter(|clause| !clause_satisfied(clause, &variables))
|
||||
.collect();
|
||||
|
||||
if unsatisfied_clauses.is_empty() {
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
let clause = unsatisfied_clauses.choose(&mut rng).unwrap();
|
||||
let literal = clause.choose(&mut rng).unwrap();
|
||||
let var_idx = literal.abs() as usize - 1;
|
||||
variables[var_idx] = !variables[var_idx];
|
||||
}
|
||||
Ok(Some(Solution { indexes }))
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// Important! Do not include any tests in this file, it will result in your submission being rejected
|
||||
fn clause_satisfied(clause: &Vec<i32>, variables: &[bool]) -> bool {
|
||||
clause.iter().any(|&literal| {
|
||||
let var_idx = literal.abs() as usize - 1;
|
||||
(literal > 0 && variables[var_idx]) || (literal < 0 && !variables[var_idx])
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
@ -1,5 +1,5 @@
|
||||
/*!
|
||||
Copyright 2024 Test
|
||||
Copyright 2024 Chad Blanchard
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
@ -14,28 +14,44 @@ language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// TIG's UI uses the pattern `tig_challenges::<challenge_name>` to automatically detect your algorithm's challenge
|
||||
use anyhow::Result;
|
||||
use tig_challenges::vector_search::{euclidean_distance, Challenge, Solution};
|
||||
use rand::prelude::*;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> Result<Option<Solution>> {
|
||||
let mut indexes = Vec::<usize>::new();
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut found = false;
|
||||
for (idx, v) in challenge.vector_database.iter().enumerate() {
|
||||
if euclidean_distance(query, v) <= challenge.max_distance {
|
||||
indexes.push(idx);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return Ok(None);
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let max_flips = 1000;
|
||||
|
||||
let mut variables: Vec<bool> = (0..num_variables).map(|_| rng.gen::<bool>()).collect();
|
||||
|
||||
for _ in 0..max_flips {
|
||||
let mut unsatisfied_clauses: Vec<&Vec<i32>> = challenge
|
||||
.clauses
|
||||
.iter()
|
||||
.filter(|clause| !clause_satisfied(clause, &variables))
|
||||
.collect();
|
||||
|
||||
if unsatisfied_clauses.is_empty() {
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
let clause = unsatisfied_clauses.choose(&mut rng).unwrap();
|
||||
let literal = clause.choose(&mut rng).unwrap();
|
||||
let var_idx = literal.abs() as usize - 1;
|
||||
variables[var_idx] = !variables[var_idx];
|
||||
}
|
||||
Ok(Some(Solution { indexes }))
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// Important! Do not include any tests in this file, it will result in your submission being rejected
|
||||
fn clause_satisfied(clause: &Vec<i32>, variables: &[bool]) -> bool {
|
||||
clause.iter().any(|&literal| {
|
||||
let var_idx = literal.abs() as usize - 1;
|
||||
(literal > 0 && variables[var_idx]) || (literal < 0 && !variables[var_idx])
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
@ -1,5 +1,5 @@
|
||||
/*!
|
||||
Copyright 2024 Test
|
||||
Copyright 2024 Chad Blanchard
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
@ -14,28 +14,44 @@ language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// TIG's UI uses the pattern `tig_challenges::<challenge_name>` to automatically detect your algorithm's challenge
|
||||
use anyhow::Result;
|
||||
use tig_challenges::vector_search::{euclidean_distance, Challenge, Solution};
|
||||
use rand::prelude::*;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> Result<Option<Solution>> {
|
||||
let mut indexes = Vec::<usize>::new();
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut found = false;
|
||||
for (idx, v) in challenge.vector_database.iter().enumerate() {
|
||||
if euclidean_distance(query, v) <= challenge.max_distance {
|
||||
indexes.push(idx);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return Ok(None);
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let max_flips = 1000;
|
||||
|
||||
let mut variables: Vec<bool> = (0..num_variables).map(|_| rng.gen::<bool>()).collect();
|
||||
|
||||
for _ in 0..max_flips {
|
||||
let mut unsatisfied_clauses: Vec<&Vec<i32>> = challenge
|
||||
.clauses
|
||||
.iter()
|
||||
.filter(|clause| !clause_satisfied(clause, &variables))
|
||||
.collect();
|
||||
|
||||
if unsatisfied_clauses.is_empty() {
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
let clause = unsatisfied_clauses.choose(&mut rng).unwrap();
|
||||
let literal = clause.choose(&mut rng).unwrap();
|
||||
let var_idx = literal.abs() as usize - 1;
|
||||
variables[var_idx] = !variables[var_idx];
|
||||
}
|
||||
Ok(Some(Solution { indexes }))
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// Important! Do not include any tests in this file, it will result in your submission being rejected
|
||||
fn clause_satisfied(clause: &Vec<i32>, variables: &[bool]) -> bool {
|
||||
clause.iter().any(|&literal| {
|
||||
let var_idx = literal.abs() as usize - 1;
|
||||
(literal > 0 && variables[var_idx]) || (literal < 0 && !variables[var_idx])
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
@ -1,5 +1,5 @@
|
||||
/*!
|
||||
Copyright 2024 Test
|
||||
Copyright 2024 Chad Blanchard
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
@ -14,28 +14,44 @@ language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// TIG's UI uses the pattern `tig_challenges::<challenge_name>` to automatically detect your algorithm's challenge
|
||||
use anyhow::Result;
|
||||
use tig_challenges::vector_search::{euclidean_distance, Challenge, Solution};
|
||||
use rand::prelude::*;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> Result<Option<Solution>> {
|
||||
let mut indexes = Vec::<usize>::new();
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut found = false;
|
||||
for (idx, v) in challenge.vector_database.iter().enumerate() {
|
||||
if euclidean_distance(query, v) <= challenge.max_distance {
|
||||
indexes.push(idx);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return Ok(None);
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let max_flips = 1000;
|
||||
|
||||
let mut variables: Vec<bool> = (0..num_variables).map(|_| rng.gen::<bool>()).collect();
|
||||
|
||||
for _ in 0..max_flips {
|
||||
let mut unsatisfied_clauses: Vec<&Vec<i32>> = challenge
|
||||
.clauses
|
||||
.iter()
|
||||
.filter(|clause| !clause_satisfied(clause, &variables))
|
||||
.collect();
|
||||
|
||||
if unsatisfied_clauses.is_empty() {
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
let clause = unsatisfied_clauses.choose(&mut rng).unwrap();
|
||||
let literal = clause.choose(&mut rng).unwrap();
|
||||
let var_idx = literal.abs() as usize - 1;
|
||||
variables[var_idx] = !variables[var_idx];
|
||||
}
|
||||
Ok(Some(Solution { indexes }))
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// Important! Do not include any tests in this file, it will result in your submission being rejected
|
||||
fn clause_satisfied(clause: &Vec<i32>, variables: &[bool]) -> bool {
|
||||
clause.iter().any(|&literal| {
|
||||
let var_idx = literal.abs() as usize - 1;
|
||||
(literal > 0 && variables[var_idx]) || (literal < 0 && !variables[var_idx])
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
4
tig-algorithms/src/satisfiability/walk_sat/mod.rs
Normal file
4
tig-algorithms/src/satisfiability/walk_sat/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod benchmarker_outbound;
|
||||
pub use benchmarker_outbound::solve_challenge;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL};
|
||||
@ -1,5 +1,5 @@
|
||||
/*!
|
||||
Copyright 2024 Test
|
||||
Copyright 2024 Chad Blanchard
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
@ -14,28 +14,44 @@ language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// TIG's UI uses the pattern `tig_challenges::<challenge_name>` to automatically detect your algorithm's challenge
|
||||
use anyhow::Result;
|
||||
use tig_challenges::vector_search::{euclidean_distance, Challenge, Solution};
|
||||
use rand::prelude::*;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
use tig_challenges::satisfiability::*;
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> Result<Option<Solution>> {
|
||||
let mut indexes = Vec::<usize>::new();
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut found = false;
|
||||
for (idx, v) in challenge.vector_database.iter().enumerate() {
|
||||
if euclidean_distance(query, v) <= challenge.max_distance {
|
||||
indexes.push(idx);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return Ok(None);
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64);
|
||||
let num_variables = challenge.difficulty.num_variables;
|
||||
let max_flips = 1000;
|
||||
|
||||
let mut variables: Vec<bool> = (0..num_variables).map(|_| rng.gen::<bool>()).collect();
|
||||
|
||||
for _ in 0..max_flips {
|
||||
let mut unsatisfied_clauses: Vec<&Vec<i32>> = challenge
|
||||
.clauses
|
||||
.iter()
|
||||
.filter(|clause| !clause_satisfied(clause, &variables))
|
||||
.collect();
|
||||
|
||||
if unsatisfied_clauses.is_empty() {
|
||||
return Ok(Some(Solution { variables }));
|
||||
}
|
||||
|
||||
let clause = unsatisfied_clauses.choose(&mut rng).unwrap();
|
||||
let literal = clause.choose(&mut rng).unwrap();
|
||||
let var_idx = literal.abs() as usize - 1;
|
||||
variables[var_idx] = !variables[var_idx];
|
||||
}
|
||||
Ok(Some(Solution { indexes }))
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// Important! Do not include any tests in this file, it will result in your submission being rejected
|
||||
fn clause_satisfied(clause: &Vec<i32>, variables: &[bool]) -> bool {
|
||||
clause.iter().any(|&literal| {
|
||||
let var_idx = literal.abs() as usize - 1;
|
||||
(literal > 0 && variables[var_idx]) || (literal < 0 && !variables[var_idx])
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
@ -0,0 +1,113 @@
|
||||
/*!
|
||||
Copyright 2024 Louis Silva
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use tig_challenges::vector_search::*;
|
||||
|
||||
#[inline]
|
||||
fn l2_norm(x: &[f32]) -> f32 {
|
||||
x.iter().map(|&val| val * val).sum::<f32>().sqrt()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn euclidean_distance_with_precomputed_norm(
|
||||
a_norm_sq: f32,
|
||||
b_norm_sq: f32,
|
||||
ab_dot_product: f32
|
||||
) -> f32 {
|
||||
(a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt()
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> Result<Option<Solution>> {
|
||||
let vector_database: &Vec<Vec<f32>> = &challenge.vector_database;
|
||||
let query_vectors: &Vec<Vec<f32>> = &challenge.query_vectors;
|
||||
let max_distance: f32 = challenge.max_distance;
|
||||
|
||||
let mut indexes: Vec<usize> = Vec::with_capacity(query_vectors.len());
|
||||
let mut vector_norms_sq: Vec<f32> = Vec::with_capacity(vector_database.len());
|
||||
|
||||
let mut sum_norms_sq: f32 = 0.0;
|
||||
let mut sum_squares: f32 = 0.0;
|
||||
|
||||
for vector in vector_database {
|
||||
let norm_sq: f32 = vector.iter().map(|&val| val * val).sum();
|
||||
sum_norms_sq += norm_sq.sqrt();
|
||||
sum_squares += norm_sq;
|
||||
vector_norms_sq.push(norm_sq);
|
||||
}
|
||||
|
||||
let vector_norms_len: f32 = vector_norms_sq.len() as f32;
|
||||
let std_dev: f32 = ((sum_squares / vector_norms_len) - (sum_norms_sq / vector_norms_len).powi(2)).sqrt();
|
||||
let norm_threshold: f32 = 2.0 * std_dev;
|
||||
|
||||
for query in query_vectors {
|
||||
let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum();
|
||||
|
||||
let mut closest_index: Option<usize> = None;
|
||||
let mut closest_distance: f32 = f32::MAX;
|
||||
|
||||
for (idx, vector) in vector_database.iter().enumerate() {
|
||||
let vector_norm_sq = vector_norms_sq[idx];
|
||||
if ((vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs()) > norm_threshold {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum();
|
||||
let distance: f32 = euclidean_distance_with_precomputed_norm(
|
||||
query_norm_sq,
|
||||
vector_norm_sq,
|
||||
ab_dot_product,
|
||||
);
|
||||
|
||||
if distance <= max_distance {
|
||||
closest_index = Some(idx);
|
||||
break; // Early exit
|
||||
} else if distance < closest_distance {
|
||||
closest_index = Some(idx);
|
||||
closest_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(index) = closest_index {
|
||||
indexes.push(index);
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Solution { indexes }))
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,113 @@
|
||||
/*!
|
||||
Copyright 2024 Louis Silva
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use tig_challenges::vector_search::*;
|
||||
|
||||
#[inline]
|
||||
fn l2_norm(x: &[f32]) -> f32 {
|
||||
x.iter().map(|&val| val * val).sum::<f32>().sqrt()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn euclidean_distance_with_precomputed_norm(
|
||||
a_norm_sq: f32,
|
||||
b_norm_sq: f32,
|
||||
ab_dot_product: f32
|
||||
) -> f32 {
|
||||
(a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt()
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> Result<Option<Solution>> {
|
||||
let vector_database: &Vec<Vec<f32>> = &challenge.vector_database;
|
||||
let query_vectors: &Vec<Vec<f32>> = &challenge.query_vectors;
|
||||
let max_distance: f32 = challenge.max_distance;
|
||||
|
||||
let mut indexes: Vec<usize> = Vec::with_capacity(query_vectors.len());
|
||||
let mut vector_norms_sq: Vec<f32> = Vec::with_capacity(vector_database.len());
|
||||
|
||||
let mut sum_norms_sq: f32 = 0.0;
|
||||
let mut sum_squares: f32 = 0.0;
|
||||
|
||||
for vector in vector_database {
|
||||
let norm_sq: f32 = vector.iter().map(|&val| val * val).sum();
|
||||
sum_norms_sq += norm_sq.sqrt();
|
||||
sum_squares += norm_sq;
|
||||
vector_norms_sq.push(norm_sq);
|
||||
}
|
||||
|
||||
let vector_norms_len: f32 = vector_norms_sq.len() as f32;
|
||||
let std_dev: f32 = ((sum_squares / vector_norms_len) - (sum_norms_sq / vector_norms_len).powi(2)).sqrt();
|
||||
let norm_threshold: f32 = 2.0 * std_dev;
|
||||
|
||||
for query in query_vectors {
|
||||
let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum();
|
||||
|
||||
let mut closest_index: Option<usize> = None;
|
||||
let mut closest_distance: f32 = f32::MAX;
|
||||
|
||||
for (idx, vector) in vector_database.iter().enumerate() {
|
||||
let vector_norm_sq = vector_norms_sq[idx];
|
||||
if ((vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs()) > norm_threshold {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum();
|
||||
let distance: f32 = euclidean_distance_with_precomputed_norm(
|
||||
query_norm_sq,
|
||||
vector_norm_sq,
|
||||
ab_dot_product,
|
||||
);
|
||||
|
||||
if distance <= max_distance {
|
||||
closest_index = Some(idx);
|
||||
break; // Early exit
|
||||
} else if distance < closest_distance {
|
||||
closest_index = Some(idx);
|
||||
closest_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(index) = closest_index {
|
||||
indexes.push(index);
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Solution { indexes }))
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
113
tig-algorithms/src/vector_search/brute_force_bacalhau/inbound.rs
Normal file
113
tig-algorithms/src/vector_search/brute_force_bacalhau/inbound.rs
Normal file
@ -0,0 +1,113 @@
|
||||
/*!
|
||||
Copyright 2024 Louis Silva
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use tig_challenges::vector_search::*;
|
||||
|
||||
#[inline]
|
||||
fn l2_norm(x: &[f32]) -> f32 {
|
||||
x.iter().map(|&val| val * val).sum::<f32>().sqrt()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn euclidean_distance_with_precomputed_norm(
|
||||
a_norm_sq: f32,
|
||||
b_norm_sq: f32,
|
||||
ab_dot_product: f32
|
||||
) -> f32 {
|
||||
(a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt()
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> Result<Option<Solution>> {
|
||||
let vector_database: &Vec<Vec<f32>> = &challenge.vector_database;
|
||||
let query_vectors: &Vec<Vec<f32>> = &challenge.query_vectors;
|
||||
let max_distance: f32 = challenge.max_distance;
|
||||
|
||||
let mut indexes: Vec<usize> = Vec::with_capacity(query_vectors.len());
|
||||
let mut vector_norms_sq: Vec<f32> = Vec::with_capacity(vector_database.len());
|
||||
|
||||
let mut sum_norms_sq: f32 = 0.0;
|
||||
let mut sum_squares: f32 = 0.0;
|
||||
|
||||
for vector in vector_database {
|
||||
let norm_sq: f32 = vector.iter().map(|&val| val * val).sum();
|
||||
sum_norms_sq += norm_sq.sqrt();
|
||||
sum_squares += norm_sq;
|
||||
vector_norms_sq.push(norm_sq);
|
||||
}
|
||||
|
||||
let vector_norms_len: f32 = vector_norms_sq.len() as f32;
|
||||
let std_dev: f32 = ((sum_squares / vector_norms_len) - (sum_norms_sq / vector_norms_len).powi(2)).sqrt();
|
||||
let norm_threshold: f32 = 2.0 * std_dev;
|
||||
|
||||
for query in query_vectors {
|
||||
let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum();
|
||||
|
||||
let mut closest_index: Option<usize> = None;
|
||||
let mut closest_distance: f32 = f32::MAX;
|
||||
|
||||
for (idx, vector) in vector_database.iter().enumerate() {
|
||||
let vector_norm_sq = vector_norms_sq[idx];
|
||||
if ((vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs()) > norm_threshold {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum();
|
||||
let distance: f32 = euclidean_distance_with_precomputed_norm(
|
||||
query_norm_sq,
|
||||
vector_norm_sq,
|
||||
ab_dot_product,
|
||||
);
|
||||
|
||||
if distance <= max_distance {
|
||||
closest_index = Some(idx);
|
||||
break; // Early exit
|
||||
} else if distance < closest_distance {
|
||||
closest_index = Some(idx);
|
||||
closest_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(index) = closest_index {
|
||||
indexes.push(index);
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Solution { indexes }))
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,113 @@
|
||||
/*!
|
||||
Copyright 2024 Louis Silva
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use tig_challenges::vector_search::*;
|
||||
|
||||
#[inline]
|
||||
fn l2_norm(x: &[f32]) -> f32 {
|
||||
x.iter().map(|&val| val * val).sum::<f32>().sqrt()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn euclidean_distance_with_precomputed_norm(
|
||||
a_norm_sq: f32,
|
||||
b_norm_sq: f32,
|
||||
ab_dot_product: f32
|
||||
) -> f32 {
|
||||
(a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt()
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> Result<Option<Solution>> {
|
||||
let vector_database: &Vec<Vec<f32>> = &challenge.vector_database;
|
||||
let query_vectors: &Vec<Vec<f32>> = &challenge.query_vectors;
|
||||
let max_distance: f32 = challenge.max_distance;
|
||||
|
||||
let mut indexes: Vec<usize> = Vec::with_capacity(query_vectors.len());
|
||||
let mut vector_norms_sq: Vec<f32> = Vec::with_capacity(vector_database.len());
|
||||
|
||||
let mut sum_norms_sq: f32 = 0.0;
|
||||
let mut sum_squares: f32 = 0.0;
|
||||
|
||||
for vector in vector_database {
|
||||
let norm_sq: f32 = vector.iter().map(|&val| val * val).sum();
|
||||
sum_norms_sq += norm_sq.sqrt();
|
||||
sum_squares += norm_sq;
|
||||
vector_norms_sq.push(norm_sq);
|
||||
}
|
||||
|
||||
let vector_norms_len: f32 = vector_norms_sq.len() as f32;
|
||||
let std_dev: f32 = ((sum_squares / vector_norms_len) - (sum_norms_sq / vector_norms_len).powi(2)).sqrt();
|
||||
let norm_threshold: f32 = 2.0 * std_dev;
|
||||
|
||||
for query in query_vectors {
|
||||
let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum();
|
||||
|
||||
let mut closest_index: Option<usize> = None;
|
||||
let mut closest_distance: f32 = f32::MAX;
|
||||
|
||||
for (idx, vector) in vector_database.iter().enumerate() {
|
||||
let vector_norm_sq = vector_norms_sq[idx];
|
||||
if ((vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs()) > norm_threshold {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum();
|
||||
let distance: f32 = euclidean_distance_with_precomputed_norm(
|
||||
query_norm_sq,
|
||||
vector_norm_sq,
|
||||
ab_dot_product,
|
||||
);
|
||||
|
||||
if distance <= max_distance {
|
||||
closest_index = Some(idx);
|
||||
break; // Early exit
|
||||
} else if distance < closest_distance {
|
||||
closest_index = Some(idx);
|
||||
closest_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(index) = closest_index {
|
||||
indexes.push(index);
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Solution { indexes }))
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,4 @@
|
||||
mod benchmarker_outbound;
|
||||
pub use benchmarker_outbound::solve_challenge;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,113 @@
|
||||
/*!
|
||||
Copyright 2024 Louis Silva
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use tig_challenges::vector_search::*;
|
||||
|
||||
#[inline]
|
||||
fn l2_norm(x: &[f32]) -> f32 {
|
||||
x.iter().map(|&val| val * val).sum::<f32>().sqrt()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn euclidean_distance_with_precomputed_norm(
|
||||
a_norm_sq: f32,
|
||||
b_norm_sq: f32,
|
||||
ab_dot_product: f32
|
||||
) -> f32 {
|
||||
(a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt()
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> Result<Option<Solution>> {
|
||||
let vector_database: &Vec<Vec<f32>> = &challenge.vector_database;
|
||||
let query_vectors: &Vec<Vec<f32>> = &challenge.query_vectors;
|
||||
let max_distance: f32 = challenge.max_distance;
|
||||
|
||||
let mut indexes: Vec<usize> = Vec::with_capacity(query_vectors.len());
|
||||
let mut vector_norms_sq: Vec<f32> = Vec::with_capacity(vector_database.len());
|
||||
|
||||
let mut sum_norms_sq: f32 = 0.0;
|
||||
let mut sum_squares: f32 = 0.0;
|
||||
|
||||
for vector in vector_database {
|
||||
let norm_sq: f32 = vector.iter().map(|&val| val * val).sum();
|
||||
sum_norms_sq += norm_sq.sqrt();
|
||||
sum_squares += norm_sq;
|
||||
vector_norms_sq.push(norm_sq);
|
||||
}
|
||||
|
||||
let vector_norms_len: f32 = vector_norms_sq.len() as f32;
|
||||
let std_dev: f32 = ((sum_squares / vector_norms_len) - (sum_norms_sq / vector_norms_len).powi(2)).sqrt();
|
||||
let norm_threshold: f32 = 2.0 * std_dev;
|
||||
|
||||
for query in query_vectors {
|
||||
let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum();
|
||||
|
||||
let mut closest_index: Option<usize> = None;
|
||||
let mut closest_distance: f32 = f32::MAX;
|
||||
|
||||
for (idx, vector) in vector_database.iter().enumerate() {
|
||||
let vector_norm_sq = vector_norms_sq[idx];
|
||||
if ((vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs()) > norm_threshold {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum();
|
||||
let distance: f32 = euclidean_distance_with_precomputed_norm(
|
||||
query_norm_sq,
|
||||
vector_norm_sq,
|
||||
ab_dot_product,
|
||||
);
|
||||
|
||||
if distance <= max_distance {
|
||||
closest_index = Some(idx);
|
||||
break; // Early exit
|
||||
} else if distance < closest_distance {
|
||||
closest_index = Some(idx);
|
||||
closest_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(index) = closest_index {
|
||||
indexes.push(index);
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Solution { indexes }))
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,367 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
use anyhow::Ok;
|
||||
use tig_challenges::vector_search::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
struct KDNode<'a> {
|
||||
point: &'a [f32],
|
||||
left: Option<Box<KDNode<'a>>>,
|
||||
right: Option<Box<KDNode<'a>>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> KDNode<'a> {
|
||||
fn new(point: &'a [f32], index: usize) -> Self {
|
||||
KDNode {
|
||||
point,
|
||||
left: None,
|
||||
right: None,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn quickselect_by<F>(arr: &mut [(&[f32], usize)], k: usize, compare: &F)
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
if arr.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let pivot_index = partition(arr, compare);
|
||||
if k < pivot_index {
|
||||
quickselect_by(&mut arr[..pivot_index], k, compare);
|
||||
} else if k > pivot_index {
|
||||
quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare);
|
||||
}
|
||||
}
|
||||
|
||||
fn partition<F>(arr: &mut [(&[f32], usize)], compare: &F) -> usize
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
let pivot_index = arr.len() >> 1;
|
||||
arr.swap(pivot_index, arr.len() - 1);
|
||||
|
||||
let mut store_index = 0;
|
||||
for i in 0..arr.len() - 1 {
|
||||
if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less {
|
||||
arr.swap(i, store_index);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
arr.swap(store_index, arr.len() - 1);
|
||||
store_index
|
||||
}
|
||||
|
||||
fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option<Box<KDNode<'a>>> {
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
const NUM_DIMENSIONS: usize = 250;
|
||||
let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new();
|
||||
let mut root: Option<Box<KDNode<'a>>> = None;
|
||||
|
||||
stack.push((0, points.len(), 0, None, false));
|
||||
|
||||
while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() {
|
||||
if start >= end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let axis = depth % NUM_DIMENSIONS;
|
||||
let median = (start + end) / 2;
|
||||
quickselect_by(&mut points[start..end], median - start, &|a, b| {
|
||||
a.0[axis].partial_cmp(&b.0[axis]).unwrap()
|
||||
});
|
||||
|
||||
let (median_point, median_index) = points[median];
|
||||
let mut new_node = Box::new(KDNode::new(median_point, median_index));
|
||||
let new_node_ptr: *mut KDNode = &mut *new_node;
|
||||
|
||||
if let Some(parent_ptr) = parent_ptr {
|
||||
unsafe {
|
||||
if is_left {
|
||||
(*parent_ptr).left = Some(new_node);
|
||||
} else {
|
||||
(*parent_ptr).right = Some(new_node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root = Some(new_node);
|
||||
}
|
||||
|
||||
stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false));
|
||||
stack.push((start, median, depth + 1, Some(new_node_ptr), true));
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..a.len() {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance_limited(a: &[f32], b: &[f32], c : f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..180 {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
if sum > c {
|
||||
sum;
|
||||
}
|
||||
for i in 180..a.len() {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
#[inline(always)]
|
||||
fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
let mut i = 0;
|
||||
let len = a.len();
|
||||
|
||||
if a.len() != b.len() || a.len() < 8 {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
while i + 7 < len {
|
||||
unsafe {
|
||||
let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1);
|
||||
let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2);
|
||||
let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3);
|
||||
let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4);
|
||||
let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5);
|
||||
let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6);
|
||||
let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7);
|
||||
|
||||
sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 +
|
||||
diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7;
|
||||
}
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 8;
|
||||
}
|
||||
|
||||
while i < len {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
fn nearest_neighbor_search<'a>(
|
||||
root: &Option<Box<KDNode<'a>>>,
|
||||
target: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
) {
|
||||
let num_dimensions = target.len();
|
||||
let mut stack = Vec::with_capacity(64);
|
||||
|
||||
if let Some(node) = root {
|
||||
stack.push((node.as_ref(), 0));
|
||||
}
|
||||
|
||||
while let Some((node, depth)) = stack.pop() {
|
||||
let axis = depth % num_dimensions;
|
||||
let dist = early_stopping_distance(&node.point, target, best.0);
|
||||
|
||||
if dist < best.0 {
|
||||
best.0 = dist;
|
||||
best.1 = Some(node.index);
|
||||
}
|
||||
|
||||
let diff = target[axis] - node.point[axis];
|
||||
let sqr_diff = diff * diff;
|
||||
|
||||
let (nearer, farther) = if diff < 0.0 {
|
||||
(&node.left, &node.right)
|
||||
} else {
|
||||
(&node.right, &node.left)
|
||||
};
|
||||
|
||||
if let Some(nearer_node) = nearer {
|
||||
stack.push((nearer_node.as_ref(), depth + 1));
|
||||
}
|
||||
|
||||
if sqr_diff < best.0 {
|
||||
if let Some(farther_node) = farther {
|
||||
stack.push((farther_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec<f32> {
|
||||
let num_vectors = vectors.len();
|
||||
let num_dimensions = 250;
|
||||
|
||||
let mut mean_vector = vec![0.0; num_dimensions];
|
||||
|
||||
for vector in vectors {
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] += vector[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] /= num_vectors as f32;
|
||||
}
|
||||
|
||||
mean_vector
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FloatOrd(f32);
|
||||
|
||||
impl PartialEq for FloatOrd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FloatOrd {}
|
||||
|
||||
impl PartialOrd for FloatOrd {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FloatOrd {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
|
||||
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
) -> Vec<(&'a [f32], usize)> {
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, vector) in database.iter().enumerate() {
|
||||
if heap.len() < k
|
||||
{
|
||||
let dist = squared_euclidean_distance(&mean_query_vector, vector);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek()
|
||||
{
|
||||
let dist = squared_euclidean_distance_limited(&mean_query_vector, vector, top_dist);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
if dist < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&'a [f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let max_fuel = 2000000000.0;
|
||||
let base_fuel = 760000000.0;
|
||||
let alpha = 1700.0 * challenge.difficulty.num_queries as f64;
|
||||
|
||||
let subset_size = ((max_fuel - base_fuel) / alpha) as usize;
|
||||
let subset = filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
);
|
||||
|
||||
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
367
tig-algorithms/src/vector_search/invector/commercial.rs
Normal file
367
tig-algorithms/src/vector_search/invector/commercial.rs
Normal file
@ -0,0 +1,367 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
use anyhow::Ok;
|
||||
use tig_challenges::vector_search::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
struct KDNode<'a> {
|
||||
point: &'a [f32],
|
||||
left: Option<Box<KDNode<'a>>>,
|
||||
right: Option<Box<KDNode<'a>>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> KDNode<'a> {
|
||||
fn new(point: &'a [f32], index: usize) -> Self {
|
||||
KDNode {
|
||||
point,
|
||||
left: None,
|
||||
right: None,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn quickselect_by<F>(arr: &mut [(&[f32], usize)], k: usize, compare: &F)
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
if arr.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let pivot_index = partition(arr, compare);
|
||||
if k < pivot_index {
|
||||
quickselect_by(&mut arr[..pivot_index], k, compare);
|
||||
} else if k > pivot_index {
|
||||
quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare);
|
||||
}
|
||||
}
|
||||
|
||||
fn partition<F>(arr: &mut [(&[f32], usize)], compare: &F) -> usize
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
let pivot_index = arr.len() >> 1;
|
||||
arr.swap(pivot_index, arr.len() - 1);
|
||||
|
||||
let mut store_index = 0;
|
||||
for i in 0..arr.len() - 1 {
|
||||
if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less {
|
||||
arr.swap(i, store_index);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
arr.swap(store_index, arr.len() - 1);
|
||||
store_index
|
||||
}
|
||||
|
||||
fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option<Box<KDNode<'a>>> {
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
const NUM_DIMENSIONS: usize = 250;
|
||||
let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new();
|
||||
let mut root: Option<Box<KDNode<'a>>> = None;
|
||||
|
||||
stack.push((0, points.len(), 0, None, false));
|
||||
|
||||
while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() {
|
||||
if start >= end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let axis = depth % NUM_DIMENSIONS;
|
||||
let median = (start + end) / 2;
|
||||
quickselect_by(&mut points[start..end], median - start, &|a, b| {
|
||||
a.0[axis].partial_cmp(&b.0[axis]).unwrap()
|
||||
});
|
||||
|
||||
let (median_point, median_index) = points[median];
|
||||
let mut new_node = Box::new(KDNode::new(median_point, median_index));
|
||||
let new_node_ptr: *mut KDNode = &mut *new_node;
|
||||
|
||||
if let Some(parent_ptr) = parent_ptr {
|
||||
unsafe {
|
||||
if is_left {
|
||||
(*parent_ptr).left = Some(new_node);
|
||||
} else {
|
||||
(*parent_ptr).right = Some(new_node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root = Some(new_node);
|
||||
}
|
||||
|
||||
stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false));
|
||||
stack.push((start, median, depth + 1, Some(new_node_ptr), true));
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..a.len() {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance_limited(a: &[f32], b: &[f32], c : f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..180 {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
if sum > c {
|
||||
sum;
|
||||
}
|
||||
for i in 180..a.len() {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
#[inline(always)]
|
||||
fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
let mut i = 0;
|
||||
let len = a.len();
|
||||
|
||||
if a.len() != b.len() || a.len() < 8 {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
while i + 7 < len {
|
||||
unsafe {
|
||||
let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1);
|
||||
let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2);
|
||||
let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3);
|
||||
let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4);
|
||||
let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5);
|
||||
let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6);
|
||||
let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7);
|
||||
|
||||
sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 +
|
||||
diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7;
|
||||
}
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 8;
|
||||
}
|
||||
|
||||
while i < len {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
fn nearest_neighbor_search<'a>(
|
||||
root: &Option<Box<KDNode<'a>>>,
|
||||
target: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
) {
|
||||
let num_dimensions = target.len();
|
||||
let mut stack = Vec::with_capacity(64);
|
||||
|
||||
if let Some(node) = root {
|
||||
stack.push((node.as_ref(), 0));
|
||||
}
|
||||
|
||||
while let Some((node, depth)) = stack.pop() {
|
||||
let axis = depth % num_dimensions;
|
||||
let dist = early_stopping_distance(&node.point, target, best.0);
|
||||
|
||||
if dist < best.0 {
|
||||
best.0 = dist;
|
||||
best.1 = Some(node.index);
|
||||
}
|
||||
|
||||
let diff = target[axis] - node.point[axis];
|
||||
let sqr_diff = diff * diff;
|
||||
|
||||
let (nearer, farther) = if diff < 0.0 {
|
||||
(&node.left, &node.right)
|
||||
} else {
|
||||
(&node.right, &node.left)
|
||||
};
|
||||
|
||||
if let Some(nearer_node) = nearer {
|
||||
stack.push((nearer_node.as_ref(), depth + 1));
|
||||
}
|
||||
|
||||
if sqr_diff < best.0 {
|
||||
if let Some(farther_node) = farther {
|
||||
stack.push((farther_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec<f32> {
|
||||
let num_vectors = vectors.len();
|
||||
let num_dimensions = 250;
|
||||
|
||||
let mut mean_vector = vec![0.0; num_dimensions];
|
||||
|
||||
for vector in vectors {
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] += vector[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] /= num_vectors as f32;
|
||||
}
|
||||
|
||||
mean_vector
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FloatOrd(f32);
|
||||
|
||||
impl PartialEq for FloatOrd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FloatOrd {}
|
||||
|
||||
impl PartialOrd for FloatOrd {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FloatOrd {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
|
||||
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
) -> Vec<(&'a [f32], usize)> {
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, vector) in database.iter().enumerate() {
|
||||
if heap.len() < k
|
||||
{
|
||||
let dist = squared_euclidean_distance(&mean_query_vector, vector);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek()
|
||||
{
|
||||
let dist = squared_euclidean_distance_limited(&mean_query_vector, vector, top_dist);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
if dist < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&'a [f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let max_fuel = 2000000000.0;
|
||||
let base_fuel = 760000000.0;
|
||||
let alpha = 1700.0 * challenge.difficulty.num_queries as f64;
|
||||
|
||||
let subset_size = ((max_fuel - base_fuel) / alpha) as usize;
|
||||
let subset = filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
);
|
||||
|
||||
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
367
tig-algorithms/src/vector_search/invector/inbound.rs
Normal file
367
tig-algorithms/src/vector_search/invector/inbound.rs
Normal file
@ -0,0 +1,367 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
use anyhow::Ok;
|
||||
use tig_challenges::vector_search::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
struct KDNode<'a> {
|
||||
point: &'a [f32],
|
||||
left: Option<Box<KDNode<'a>>>,
|
||||
right: Option<Box<KDNode<'a>>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> KDNode<'a> {
|
||||
fn new(point: &'a [f32], index: usize) -> Self {
|
||||
KDNode {
|
||||
point,
|
||||
left: None,
|
||||
right: None,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn quickselect_by<F>(arr: &mut [(&[f32], usize)], k: usize, compare: &F)
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
if arr.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let pivot_index = partition(arr, compare);
|
||||
if k < pivot_index {
|
||||
quickselect_by(&mut arr[..pivot_index], k, compare);
|
||||
} else if k > pivot_index {
|
||||
quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare);
|
||||
}
|
||||
}
|
||||
|
||||
fn partition<F>(arr: &mut [(&[f32], usize)], compare: &F) -> usize
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
let pivot_index = arr.len() >> 1;
|
||||
arr.swap(pivot_index, arr.len() - 1);
|
||||
|
||||
let mut store_index = 0;
|
||||
for i in 0..arr.len() - 1 {
|
||||
if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less {
|
||||
arr.swap(i, store_index);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
arr.swap(store_index, arr.len() - 1);
|
||||
store_index
|
||||
}
|
||||
|
||||
fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option<Box<KDNode<'a>>> {
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
const NUM_DIMENSIONS: usize = 250;
|
||||
let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new();
|
||||
let mut root: Option<Box<KDNode<'a>>> = None;
|
||||
|
||||
stack.push((0, points.len(), 0, None, false));
|
||||
|
||||
while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() {
|
||||
if start >= end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let axis = depth % NUM_DIMENSIONS;
|
||||
let median = (start + end) / 2;
|
||||
quickselect_by(&mut points[start..end], median - start, &|a, b| {
|
||||
a.0[axis].partial_cmp(&b.0[axis]).unwrap()
|
||||
});
|
||||
|
||||
let (median_point, median_index) = points[median];
|
||||
let mut new_node = Box::new(KDNode::new(median_point, median_index));
|
||||
let new_node_ptr: *mut KDNode = &mut *new_node;
|
||||
|
||||
if let Some(parent_ptr) = parent_ptr {
|
||||
unsafe {
|
||||
if is_left {
|
||||
(*parent_ptr).left = Some(new_node);
|
||||
} else {
|
||||
(*parent_ptr).right = Some(new_node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root = Some(new_node);
|
||||
}
|
||||
|
||||
stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false));
|
||||
stack.push((start, median, depth + 1, Some(new_node_ptr), true));
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..a.len() {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance_limited(a: &[f32], b: &[f32], c : f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..180 {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
if sum > c {
|
||||
sum;
|
||||
}
|
||||
for i in 180..a.len() {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
#[inline(always)]
|
||||
fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
let mut i = 0;
|
||||
let len = a.len();
|
||||
|
||||
if a.len() != b.len() || a.len() < 8 {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
while i + 7 < len {
|
||||
unsafe {
|
||||
let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1);
|
||||
let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2);
|
||||
let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3);
|
||||
let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4);
|
||||
let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5);
|
||||
let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6);
|
||||
let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7);
|
||||
|
||||
sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 +
|
||||
diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7;
|
||||
}
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 8;
|
||||
}
|
||||
|
||||
while i < len {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
fn nearest_neighbor_search<'a>(
|
||||
root: &Option<Box<KDNode<'a>>>,
|
||||
target: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
) {
|
||||
let num_dimensions = target.len();
|
||||
let mut stack = Vec::with_capacity(64);
|
||||
|
||||
if let Some(node) = root {
|
||||
stack.push((node.as_ref(), 0));
|
||||
}
|
||||
|
||||
while let Some((node, depth)) = stack.pop() {
|
||||
let axis = depth % num_dimensions;
|
||||
let dist = early_stopping_distance(&node.point, target, best.0);
|
||||
|
||||
if dist < best.0 {
|
||||
best.0 = dist;
|
||||
best.1 = Some(node.index);
|
||||
}
|
||||
|
||||
let diff = target[axis] - node.point[axis];
|
||||
let sqr_diff = diff * diff;
|
||||
|
||||
let (nearer, farther) = if diff < 0.0 {
|
||||
(&node.left, &node.right)
|
||||
} else {
|
||||
(&node.right, &node.left)
|
||||
};
|
||||
|
||||
if let Some(nearer_node) = nearer {
|
||||
stack.push((nearer_node.as_ref(), depth + 1));
|
||||
}
|
||||
|
||||
if sqr_diff < best.0 {
|
||||
if let Some(farther_node) = farther {
|
||||
stack.push((farther_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec<f32> {
|
||||
let num_vectors = vectors.len();
|
||||
let num_dimensions = 250;
|
||||
|
||||
let mut mean_vector = vec![0.0; num_dimensions];
|
||||
|
||||
for vector in vectors {
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] += vector[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] /= num_vectors as f32;
|
||||
}
|
||||
|
||||
mean_vector
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FloatOrd(f32);
|
||||
|
||||
impl PartialEq for FloatOrd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FloatOrd {}
|
||||
|
||||
impl PartialOrd for FloatOrd {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FloatOrd {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
|
||||
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
) -> Vec<(&'a [f32], usize)> {
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, vector) in database.iter().enumerate() {
|
||||
if heap.len() < k
|
||||
{
|
||||
let dist = squared_euclidean_distance(&mean_query_vector, vector);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek()
|
||||
{
|
||||
let dist = squared_euclidean_distance_limited(&mean_query_vector, vector, top_dist);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
if dist < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&'a [f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let max_fuel = 2000000000.0;
|
||||
let base_fuel = 760000000.0;
|
||||
let alpha = 1700.0 * challenge.difficulty.num_queries as f64;
|
||||
|
||||
let subset_size = ((max_fuel - base_fuel) / alpha) as usize;
|
||||
let subset = filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
);
|
||||
|
||||
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
367
tig-algorithms/src/vector_search/invector/innovator_outbound.rs
Normal file
367
tig-algorithms/src/vector_search/invector/innovator_outbound.rs
Normal file
@ -0,0 +1,367 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
use anyhow::Ok;
|
||||
use tig_challenges::vector_search::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
struct KDNode<'a> {
|
||||
point: &'a [f32],
|
||||
left: Option<Box<KDNode<'a>>>,
|
||||
right: Option<Box<KDNode<'a>>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> KDNode<'a> {
|
||||
fn new(point: &'a [f32], index: usize) -> Self {
|
||||
KDNode {
|
||||
point,
|
||||
left: None,
|
||||
right: None,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn quickselect_by<F>(arr: &mut [(&[f32], usize)], k: usize, compare: &F)
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
if arr.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let pivot_index = partition(arr, compare);
|
||||
if k < pivot_index {
|
||||
quickselect_by(&mut arr[..pivot_index], k, compare);
|
||||
} else if k > pivot_index {
|
||||
quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare);
|
||||
}
|
||||
}
|
||||
|
||||
fn partition<F>(arr: &mut [(&[f32], usize)], compare: &F) -> usize
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
let pivot_index = arr.len() >> 1;
|
||||
arr.swap(pivot_index, arr.len() - 1);
|
||||
|
||||
let mut store_index = 0;
|
||||
for i in 0..arr.len() - 1 {
|
||||
if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less {
|
||||
arr.swap(i, store_index);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
arr.swap(store_index, arr.len() - 1);
|
||||
store_index
|
||||
}
|
||||
|
||||
fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option<Box<KDNode<'a>>> {
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
const NUM_DIMENSIONS: usize = 250;
|
||||
let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new();
|
||||
let mut root: Option<Box<KDNode<'a>>> = None;
|
||||
|
||||
stack.push((0, points.len(), 0, None, false));
|
||||
|
||||
while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() {
|
||||
if start >= end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let axis = depth % NUM_DIMENSIONS;
|
||||
let median = (start + end) / 2;
|
||||
quickselect_by(&mut points[start..end], median - start, &|a, b| {
|
||||
a.0[axis].partial_cmp(&b.0[axis]).unwrap()
|
||||
});
|
||||
|
||||
let (median_point, median_index) = points[median];
|
||||
let mut new_node = Box::new(KDNode::new(median_point, median_index));
|
||||
let new_node_ptr: *mut KDNode = &mut *new_node;
|
||||
|
||||
if let Some(parent_ptr) = parent_ptr {
|
||||
unsafe {
|
||||
if is_left {
|
||||
(*parent_ptr).left = Some(new_node);
|
||||
} else {
|
||||
(*parent_ptr).right = Some(new_node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root = Some(new_node);
|
||||
}
|
||||
|
||||
stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false));
|
||||
stack.push((start, median, depth + 1, Some(new_node_ptr), true));
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..a.len() {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance_limited(a: &[f32], b: &[f32], c : f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..180 {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
if sum > c {
|
||||
sum;
|
||||
}
|
||||
for i in 180..a.len() {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
#[inline(always)]
|
||||
fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
let mut i = 0;
|
||||
let len = a.len();
|
||||
|
||||
if a.len() != b.len() || a.len() < 8 {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
while i + 7 < len {
|
||||
unsafe {
|
||||
let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1);
|
||||
let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2);
|
||||
let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3);
|
||||
let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4);
|
||||
let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5);
|
||||
let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6);
|
||||
let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7);
|
||||
|
||||
sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 +
|
||||
diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7;
|
||||
}
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 8;
|
||||
}
|
||||
|
||||
while i < len {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
fn nearest_neighbor_search<'a>(
|
||||
root: &Option<Box<KDNode<'a>>>,
|
||||
target: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
) {
|
||||
let num_dimensions = target.len();
|
||||
let mut stack = Vec::with_capacity(64);
|
||||
|
||||
if let Some(node) = root {
|
||||
stack.push((node.as_ref(), 0));
|
||||
}
|
||||
|
||||
while let Some((node, depth)) = stack.pop() {
|
||||
let axis = depth % num_dimensions;
|
||||
let dist = early_stopping_distance(&node.point, target, best.0);
|
||||
|
||||
if dist < best.0 {
|
||||
best.0 = dist;
|
||||
best.1 = Some(node.index);
|
||||
}
|
||||
|
||||
let diff = target[axis] - node.point[axis];
|
||||
let sqr_diff = diff * diff;
|
||||
|
||||
let (nearer, farther) = if diff < 0.0 {
|
||||
(&node.left, &node.right)
|
||||
} else {
|
||||
(&node.right, &node.left)
|
||||
};
|
||||
|
||||
if let Some(nearer_node) = nearer {
|
||||
stack.push((nearer_node.as_ref(), depth + 1));
|
||||
}
|
||||
|
||||
if sqr_diff < best.0 {
|
||||
if let Some(farther_node) = farther {
|
||||
stack.push((farther_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec<f32> {
|
||||
let num_vectors = vectors.len();
|
||||
let num_dimensions = 250;
|
||||
|
||||
let mut mean_vector = vec![0.0; num_dimensions];
|
||||
|
||||
for vector in vectors {
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] += vector[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] /= num_vectors as f32;
|
||||
}
|
||||
|
||||
mean_vector
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FloatOrd(f32);
|
||||
|
||||
impl PartialEq for FloatOrd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FloatOrd {}
|
||||
|
||||
impl PartialOrd for FloatOrd {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FloatOrd {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
|
||||
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
) -> Vec<(&'a [f32], usize)> {
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, vector) in database.iter().enumerate() {
|
||||
if heap.len() < k
|
||||
{
|
||||
let dist = squared_euclidean_distance(&mean_query_vector, vector);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek()
|
||||
{
|
||||
let dist = squared_euclidean_distance_limited(&mean_query_vector, vector, top_dist);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
if dist < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&'a [f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let max_fuel = 2000000000.0;
|
||||
let base_fuel = 760000000.0;
|
||||
let alpha = 1700.0 * challenge.difficulty.num_queries as f64;
|
||||
|
||||
let subset_size = ((max_fuel - base_fuel) / alpha) as usize;
|
||||
let subset = filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
);
|
||||
|
||||
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
4
tig-algorithms/src/vector_search/invector/mod.rs
Normal file
4
tig-algorithms/src/vector_search/invector/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod benchmarker_outbound;
|
||||
pub use benchmarker_outbound::solve_challenge;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL};
|
||||
367
tig-algorithms/src/vector_search/invector/open_data.rs
Normal file
367
tig-algorithms/src/vector_search/invector/open_data.rs
Normal file
@ -0,0 +1,367 @@
|
||||
/*!
|
||||
Copyright 2024 syebastian
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
use anyhow::Ok;
|
||||
use tig_challenges::vector_search::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
struct KDNode<'a> {
|
||||
point: &'a [f32],
|
||||
left: Option<Box<KDNode<'a>>>,
|
||||
right: Option<Box<KDNode<'a>>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> KDNode<'a> {
|
||||
fn new(point: &'a [f32], index: usize) -> Self {
|
||||
KDNode {
|
||||
point,
|
||||
left: None,
|
||||
right: None,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn quickselect_by<F>(arr: &mut [(&[f32], usize)], k: usize, compare: &F)
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
if arr.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let pivot_index = partition(arr, compare);
|
||||
if k < pivot_index {
|
||||
quickselect_by(&mut arr[..pivot_index], k, compare);
|
||||
} else if k > pivot_index {
|
||||
quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare);
|
||||
}
|
||||
}
|
||||
|
||||
fn partition<F>(arr: &mut [(&[f32], usize)], compare: &F) -> usize
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
let pivot_index = arr.len() >> 1;
|
||||
arr.swap(pivot_index, arr.len() - 1);
|
||||
|
||||
let mut store_index = 0;
|
||||
for i in 0..arr.len() - 1 {
|
||||
if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less {
|
||||
arr.swap(i, store_index);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
arr.swap(store_index, arr.len() - 1);
|
||||
store_index
|
||||
}
|
||||
|
||||
fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option<Box<KDNode<'a>>> {
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
const NUM_DIMENSIONS: usize = 250;
|
||||
let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new();
|
||||
let mut root: Option<Box<KDNode<'a>>> = None;
|
||||
|
||||
stack.push((0, points.len(), 0, None, false));
|
||||
|
||||
while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() {
|
||||
if start >= end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let axis = depth % NUM_DIMENSIONS;
|
||||
let median = (start + end) / 2;
|
||||
quickselect_by(&mut points[start..end], median - start, &|a, b| {
|
||||
a.0[axis].partial_cmp(&b.0[axis]).unwrap()
|
||||
});
|
||||
|
||||
let (median_point, median_index) = points[median];
|
||||
let mut new_node = Box::new(KDNode::new(median_point, median_index));
|
||||
let new_node_ptr: *mut KDNode = &mut *new_node;
|
||||
|
||||
if let Some(parent_ptr) = parent_ptr {
|
||||
unsafe {
|
||||
if is_left {
|
||||
(*parent_ptr).left = Some(new_node);
|
||||
} else {
|
||||
(*parent_ptr).right = Some(new_node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root = Some(new_node);
|
||||
}
|
||||
|
||||
stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false));
|
||||
stack.push((start, median, depth + 1, Some(new_node_ptr), true));
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..a.len() {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance_limited(a: &[f32], b: &[f32], c : f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..180 {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
if sum > c {
|
||||
sum;
|
||||
}
|
||||
for i in 180..a.len() {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
#[inline(always)]
|
||||
fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
let mut i = 0;
|
||||
let len = a.len();
|
||||
|
||||
if a.len() != b.len() || a.len() < 8 {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
while i + 7 < len {
|
||||
unsafe {
|
||||
let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1);
|
||||
let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2);
|
||||
let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3);
|
||||
let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4);
|
||||
let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5);
|
||||
let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6);
|
||||
let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7);
|
||||
|
||||
sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 +
|
||||
diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7;
|
||||
}
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 8;
|
||||
}
|
||||
|
||||
while i < len {
|
||||
unsafe {
|
||||
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
||||
sum += diff * diff;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
fn nearest_neighbor_search<'a>(
|
||||
root: &Option<Box<KDNode<'a>>>,
|
||||
target: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
) {
|
||||
let num_dimensions = target.len();
|
||||
let mut stack = Vec::with_capacity(64);
|
||||
|
||||
if let Some(node) = root {
|
||||
stack.push((node.as_ref(), 0));
|
||||
}
|
||||
|
||||
while let Some((node, depth)) = stack.pop() {
|
||||
let axis = depth % num_dimensions;
|
||||
let dist = early_stopping_distance(&node.point, target, best.0);
|
||||
|
||||
if dist < best.0 {
|
||||
best.0 = dist;
|
||||
best.1 = Some(node.index);
|
||||
}
|
||||
|
||||
let diff = target[axis] - node.point[axis];
|
||||
let sqr_diff = diff * diff;
|
||||
|
||||
let (nearer, farther) = if diff < 0.0 {
|
||||
(&node.left, &node.right)
|
||||
} else {
|
||||
(&node.right, &node.left)
|
||||
};
|
||||
|
||||
if let Some(nearer_node) = nearer {
|
||||
stack.push((nearer_node.as_ref(), depth + 1));
|
||||
}
|
||||
|
||||
if sqr_diff < best.0 {
|
||||
if let Some(farther_node) = farther {
|
||||
stack.push((farther_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec<f32> {
|
||||
let num_vectors = vectors.len();
|
||||
let num_dimensions = 250;
|
||||
|
||||
let mut mean_vector = vec![0.0; num_dimensions];
|
||||
|
||||
for vector in vectors {
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] += vector[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] /= num_vectors as f32;
|
||||
}
|
||||
|
||||
mean_vector
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FloatOrd(f32);
|
||||
|
||||
impl PartialEq for FloatOrd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FloatOrd {}
|
||||
|
||||
impl PartialOrd for FloatOrd {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FloatOrd {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
|
||||
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
) -> Vec<(&'a [f32], usize)> {
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, vector) in database.iter().enumerate() {
|
||||
if heap.len() < k
|
||||
{
|
||||
let dist = squared_euclidean_distance(&mean_query_vector, vector);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek()
|
||||
{
|
||||
let dist = squared_euclidean_distance_limited(&mean_query_vector, vector, top_dist);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
if dist < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&'a [f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let max_fuel = 2000000000.0;
|
||||
let base_fuel = 760000000.0;
|
||||
let alpha = 1700.0 * challenge.difficulty.num_queries as f64;
|
||||
|
||||
let subset_size = ((max_fuel - base_fuel) / alpha) as usize;
|
||||
let subset = filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
);
|
||||
|
||||
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
|
||||
// set KERNEL to None if algorithm only has a CPU implementation
|
||||
pub const KERNEL: Option<CudaKernel> = None;
|
||||
|
||||
// Important! your GPU and CPU version of the algorithm should return the same result
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
solve_challenge(challenge)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -1,5 +1,4 @@
|
||||
pub mod basic;
|
||||
pub use basic as c004_a001;
|
||||
// c004_a001
|
||||
|
||||
// c004_a002
|
||||
|
||||
@ -25,7 +24,8 @@ pub use basic as c004_a001;
|
||||
|
||||
// c004_a013
|
||||
|
||||
// c004_a014
|
||||
pub mod brute_force_bacalhau;
|
||||
pub use brute_force_bacalhau as c004_a014;
|
||||
|
||||
// c004_a015
|
||||
|
||||
@ -49,7 +49,8 @@ pub use basic as c004_a001;
|
||||
|
||||
// c004_a025
|
||||
|
||||
// c004_a026
|
||||
pub mod optimax_gpu;
|
||||
pub use optimax_gpu as c004_a026;
|
||||
|
||||
// c004_a027
|
||||
|
||||
@ -65,7 +66,8 @@ pub use basic as c004_a001;
|
||||
|
||||
// c004_a033
|
||||
|
||||
// c004_a034
|
||||
pub mod invector;
|
||||
pub use invector as c004_a034;
|
||||
|
||||
// c004_a035
|
||||
|
||||
|
||||
@ -0,0 +1,468 @@
|
||||
/*!
|
||||
Copyright 2024 bw-dev36
|
||||
|
||||
Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::Ok;
|
||||
use tig_challenges::vector_search::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
struct KDNode<'a> {
|
||||
point: &'a [f32],
|
||||
left: Option<Box<KDNode<'a>>>,
|
||||
right: Option<Box<KDNode<'a>>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> KDNode<'a> {
|
||||
fn new(point: &'a [f32], index: usize) -> Self {
|
||||
KDNode {
|
||||
point,
|
||||
left: None,
|
||||
right: None,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn quickselect_by<F>(arr: &mut [(&[f32], usize)], k: usize, compare: &F)
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
if arr.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let pivot_index = partition(arr, compare);
|
||||
if k < pivot_index {
|
||||
quickselect_by(&mut arr[..pivot_index], k, compare);
|
||||
} else if k > pivot_index {
|
||||
quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare);
|
||||
}
|
||||
}
|
||||
|
||||
fn partition<F>(arr: &mut [(&[f32], usize)], compare: &F) -> usize
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
let pivot_index = arr.len() >> 1;
|
||||
arr.swap(pivot_index, arr.len() - 1);
|
||||
|
||||
let mut store_index = 0;
|
||||
for i in 0..arr.len() - 1 {
|
||||
if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less {
|
||||
arr.swap(i, store_index);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
arr.swap(store_index, arr.len() - 1);
|
||||
store_index
|
||||
}
|
||||
|
||||
fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option<Box<KDNode<'a>>> {
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
const NUM_DIMENSIONS: usize = 250;
|
||||
let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new();
|
||||
let mut root: Option<Box<KDNode<'a>>> = None;
|
||||
|
||||
stack.push((0, points.len(), 0, None, false));
|
||||
|
||||
while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() {
|
||||
if start >= end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let axis = depth % NUM_DIMENSIONS;
|
||||
let median = (start + end) / 2;
|
||||
quickselect_by(&mut points[start..end], median - start, &|a, b| {
|
||||
a.0[axis].partial_cmp(&b.0[axis]).unwrap()
|
||||
});
|
||||
|
||||
let (median_point, median_index) = points[median];
|
||||
let mut new_node = Box::new(KDNode::new(median_point, median_index));
|
||||
let new_node_ptr: *mut KDNode = &mut *new_node;
|
||||
|
||||
if let Some(parent_ptr) = parent_ptr {
|
||||
unsafe {
|
||||
if is_left {
|
||||
(*parent_ptr).left = Some(new_node);
|
||||
} else {
|
||||
(*parent_ptr).right = Some(new_node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root = Some(new_node);
|
||||
}
|
||||
|
||||
stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false));
|
||||
stack.push((start, median, depth + 1, Some(new_node_ptr), true));
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..a.len() {
|
||||
let diff = a[i] - b[i];
|
||||
sum += diff * diff;
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
let mut i = 0;
|
||||
while i + 3 < a.len() {
|
||||
let diff0 = a[i] - b[i];
|
||||
let diff1 = a[i + 1] - b[i + 1];
|
||||
let diff2 = a[i + 2] - b[i + 2];
|
||||
let diff3 = a[i + 3] - b[i + 3];
|
||||
|
||||
sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3;
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 4;
|
||||
}
|
||||
|
||||
while i < a.len() {
|
||||
let diff = a[i] - b[i];
|
||||
sum += diff * diff;
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
sum
|
||||
}
|
||||
|
||||
fn nearest_neighbor_search<'a>(
|
||||
root: &Option<Box<KDNode<'a>>>,
|
||||
target: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
) {
|
||||
let num_dimensions = target.len();
|
||||
let mut stack = Vec::with_capacity(64);
|
||||
|
||||
if let Some(node) = root {
|
||||
stack.push((node.as_ref(), 0));
|
||||
}
|
||||
|
||||
while let Some((node, depth)) = stack.pop() {
|
||||
let axis = depth % num_dimensions;
|
||||
let dist = early_stopping_distance(&node.point, target, best.0);
|
||||
|
||||
if dist < best.0 {
|
||||
best.0 = dist;
|
||||
best.1 = Some(node.index);
|
||||
}
|
||||
|
||||
let diff = target[axis] - node.point[axis];
|
||||
let sqr_diff = diff * diff;
|
||||
|
||||
if sqr_diff < best.0 {
|
||||
if let Some(farther_node) = if diff < 0.0 { &node.right } else { &node.left } {
|
||||
stack.push((farther_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(nearer_node) = if diff < 0.0 { &node.left } else { &node.right } {
|
||||
stack.push((nearer_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec<f32> {
|
||||
let num_vectors = vectors.len();
|
||||
let num_dimensions = 250;
|
||||
|
||||
let mut mean_vector = vec![0.0; num_dimensions];
|
||||
|
||||
for vector in vectors {
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] += vector[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] /= num_vectors as f32;
|
||||
}
|
||||
|
||||
mean_vector
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FloatOrd(f32);
|
||||
|
||||
impl PartialEq for FloatOrd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FloatOrd {}
|
||||
|
||||
impl PartialOrd for FloatOrd {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FloatOrd {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
|
||||
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
) -> Vec<(&'a [f32], usize)> {
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, vector) in database.iter().enumerate() {
|
||||
let dist = squared_euclidean_distance(&mean_query_vector, vector);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
if heap.len() < k {
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() {
|
||||
if dist < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&'a [f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let subset_size = match query_count {
|
||||
10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200,
|
||||
10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200,
|
||||
20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000,
|
||||
20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel
|
||||
29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000,
|
||||
29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000,
|
||||
46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel
|
||||
51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000,
|
||||
51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel
|
||||
71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500,
|
||||
71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel
|
||||
_ => 1000, // need more fuel
|
||||
};
|
||||
let subset = filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
);
|
||||
|
||||
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
pub const KERNEL: Option<CudaKernel> = Some(CudaKernel {
|
||||
src: r#"
|
||||
|
||||
extern "C" __global__ void filter_vectors(float* query_mean, float* vectors, float* distances, int num_vectors, int num_dimensions) {
|
||||
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (idx < num_vectors) {
|
||||
float dist = 0.0;
|
||||
for (int d = 0; d < num_dimensions; ++d) {
|
||||
float diff = query_mean[d] - vectors[idx * num_dimensions + d];
|
||||
dist += diff * diff;
|
||||
}
|
||||
distances[idx] = dist;
|
||||
}
|
||||
}
|
||||
|
||||
"#,
|
||||
|
||||
funcs: &["filter_vectors"],
|
||||
});
|
||||
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let subset_size = match query_count {
|
||||
10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200,
|
||||
10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200,
|
||||
20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000,
|
||||
20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel
|
||||
29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000,
|
||||
29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000,
|
||||
46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel
|
||||
51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000,
|
||||
51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel
|
||||
71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500,
|
||||
71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel
|
||||
_ => 1000, // need more fuel
|
||||
};
|
||||
let subset = cuda_filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
dev,
|
||||
funcs,
|
||||
)?;
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Vec<(&'a [f32], usize)>> {
|
||||
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let num_vectors = database.len();
|
||||
let num_dimensions = 250;
|
||||
let flattened_database: Vec<f32> = database.iter().flatten().cloned().collect();
|
||||
let database_dev = dev.htod_sync_copy(&flattened_database)?;
|
||||
let mean_query_dev = dev.htod_sync_copy(&mean_query_vector)?;
|
||||
let mut distances_dev = dev.alloc_zeros::<f32>(num_vectors)?;
|
||||
let cfg = LaunchConfig {
|
||||
block_dim: (256, 1, 1),
|
||||
grid_dim: ((num_vectors as u32 + 255) / 256, 1, 1),
|
||||
shared_mem_bytes: 0,
|
||||
};
|
||||
unsafe {
|
||||
funcs.remove("filter_vectors").unwrap().launch(
|
||||
cfg,
|
||||
(
|
||||
&mean_query_dev,
|
||||
&database_dev,
|
||||
&mut distances_dev,
|
||||
num_vectors as i32,
|
||||
num_dimensions as i32,
|
||||
),
|
||||
)
|
||||
}?;
|
||||
let mut distances_host = vec![0.0f32; num_vectors];
|
||||
dev.dtoh_sync_copy_into(&distances_dev, &mut distances_host)?;
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, &distance) in distances_host.iter().enumerate() {
|
||||
let ord_dist = FloatOrd(distance);
|
||||
if heap.len() < k {
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() {
|
||||
if distance < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&[f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_build_kd_tree<'a>(subset: &mut [(&'a [f32], usize)],
|
||||
dev: &Arc<CudaDevice>,
|
||||
funcs: &mut HashMap<&'static str, CudaFunction>,
|
||||
) -> Option<Box<KDNode<'a>>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_nearest_neighbor_search(
|
||||
kd_tree: &Option<Box<KDNode<'_>>>,
|
||||
query: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
dev: &Arc<CudaDevice>,
|
||||
funcs: &mut HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
468
tig-algorithms/src/vector_search/optimax_gpu/commercial.rs
Normal file
468
tig-algorithms/src/vector_search/optimax_gpu/commercial.rs
Normal file
@ -0,0 +1,468 @@
|
||||
/*!
|
||||
Copyright 2024 bw-dev36
|
||||
|
||||
Licensed under the TIG Commercial License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::Ok;
|
||||
use tig_challenges::vector_search::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
struct KDNode<'a> {
|
||||
point: &'a [f32],
|
||||
left: Option<Box<KDNode<'a>>>,
|
||||
right: Option<Box<KDNode<'a>>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> KDNode<'a> {
|
||||
fn new(point: &'a [f32], index: usize) -> Self {
|
||||
KDNode {
|
||||
point,
|
||||
left: None,
|
||||
right: None,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn quickselect_by<F>(arr: &mut [(&[f32], usize)], k: usize, compare: &F)
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
if arr.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let pivot_index = partition(arr, compare);
|
||||
if k < pivot_index {
|
||||
quickselect_by(&mut arr[..pivot_index], k, compare);
|
||||
} else if k > pivot_index {
|
||||
quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare);
|
||||
}
|
||||
}
|
||||
|
||||
fn partition<F>(arr: &mut [(&[f32], usize)], compare: &F) -> usize
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
let pivot_index = arr.len() >> 1;
|
||||
arr.swap(pivot_index, arr.len() - 1);
|
||||
|
||||
let mut store_index = 0;
|
||||
for i in 0..arr.len() - 1 {
|
||||
if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less {
|
||||
arr.swap(i, store_index);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
arr.swap(store_index, arr.len() - 1);
|
||||
store_index
|
||||
}
|
||||
|
||||
fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option<Box<KDNode<'a>>> {
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
const NUM_DIMENSIONS: usize = 250;
|
||||
let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new();
|
||||
let mut root: Option<Box<KDNode<'a>>> = None;
|
||||
|
||||
stack.push((0, points.len(), 0, None, false));
|
||||
|
||||
while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() {
|
||||
if start >= end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let axis = depth % NUM_DIMENSIONS;
|
||||
let median = (start + end) / 2;
|
||||
quickselect_by(&mut points[start..end], median - start, &|a, b| {
|
||||
a.0[axis].partial_cmp(&b.0[axis]).unwrap()
|
||||
});
|
||||
|
||||
let (median_point, median_index) = points[median];
|
||||
let mut new_node = Box::new(KDNode::new(median_point, median_index));
|
||||
let new_node_ptr: *mut KDNode = &mut *new_node;
|
||||
|
||||
if let Some(parent_ptr) = parent_ptr {
|
||||
unsafe {
|
||||
if is_left {
|
||||
(*parent_ptr).left = Some(new_node);
|
||||
} else {
|
||||
(*parent_ptr).right = Some(new_node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root = Some(new_node);
|
||||
}
|
||||
|
||||
stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false));
|
||||
stack.push((start, median, depth + 1, Some(new_node_ptr), true));
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..a.len() {
|
||||
let diff = a[i] - b[i];
|
||||
sum += diff * diff;
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
let mut i = 0;
|
||||
while i + 3 < a.len() {
|
||||
let diff0 = a[i] - b[i];
|
||||
let diff1 = a[i + 1] - b[i + 1];
|
||||
let diff2 = a[i + 2] - b[i + 2];
|
||||
let diff3 = a[i + 3] - b[i + 3];
|
||||
|
||||
sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3;
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 4;
|
||||
}
|
||||
|
||||
while i < a.len() {
|
||||
let diff = a[i] - b[i];
|
||||
sum += diff * diff;
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
sum
|
||||
}
|
||||
|
||||
fn nearest_neighbor_search<'a>(
|
||||
root: &Option<Box<KDNode<'a>>>,
|
||||
target: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
) {
|
||||
let num_dimensions = target.len();
|
||||
let mut stack = Vec::with_capacity(64);
|
||||
|
||||
if let Some(node) = root {
|
||||
stack.push((node.as_ref(), 0));
|
||||
}
|
||||
|
||||
while let Some((node, depth)) = stack.pop() {
|
||||
let axis = depth % num_dimensions;
|
||||
let dist = early_stopping_distance(&node.point, target, best.0);
|
||||
|
||||
if dist < best.0 {
|
||||
best.0 = dist;
|
||||
best.1 = Some(node.index);
|
||||
}
|
||||
|
||||
let diff = target[axis] - node.point[axis];
|
||||
let sqr_diff = diff * diff;
|
||||
|
||||
if sqr_diff < best.0 {
|
||||
if let Some(farther_node) = if diff < 0.0 { &node.right } else { &node.left } {
|
||||
stack.push((farther_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(nearer_node) = if diff < 0.0 { &node.left } else { &node.right } {
|
||||
stack.push((nearer_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec<f32> {
|
||||
let num_vectors = vectors.len();
|
||||
let num_dimensions = 250;
|
||||
|
||||
let mut mean_vector = vec![0.0; num_dimensions];
|
||||
|
||||
for vector in vectors {
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] += vector[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] /= num_vectors as f32;
|
||||
}
|
||||
|
||||
mean_vector
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FloatOrd(f32);
|
||||
|
||||
impl PartialEq for FloatOrd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FloatOrd {}
|
||||
|
||||
impl PartialOrd for FloatOrd {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FloatOrd {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
|
||||
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
) -> Vec<(&'a [f32], usize)> {
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, vector) in database.iter().enumerate() {
|
||||
let dist = squared_euclidean_distance(&mean_query_vector, vector);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
if heap.len() < k {
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() {
|
||||
if dist < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&'a [f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let subset_size = match query_count {
|
||||
10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200,
|
||||
10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200,
|
||||
20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000,
|
||||
20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel
|
||||
29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000,
|
||||
29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000,
|
||||
46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel
|
||||
51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000,
|
||||
51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel
|
||||
71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500,
|
||||
71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel
|
||||
_ => 1000, // need more fuel
|
||||
};
|
||||
let subset = filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
);
|
||||
|
||||
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
pub const KERNEL: Option<CudaKernel> = Some(CudaKernel {
|
||||
src: r#"
|
||||
|
||||
extern "C" __global__ void filter_vectors(float* query_mean, float* vectors, float* distances, int num_vectors, int num_dimensions) {
|
||||
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (idx < num_vectors) {
|
||||
float dist = 0.0;
|
||||
for (int d = 0; d < num_dimensions; ++d) {
|
||||
float diff = query_mean[d] - vectors[idx * num_dimensions + d];
|
||||
dist += diff * diff;
|
||||
}
|
||||
distances[idx] = dist;
|
||||
}
|
||||
}
|
||||
|
||||
"#,
|
||||
|
||||
funcs: &["filter_vectors"],
|
||||
});
|
||||
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let subset_size = match query_count {
|
||||
10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200,
|
||||
10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200,
|
||||
20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000,
|
||||
20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel
|
||||
29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000,
|
||||
29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000,
|
||||
46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel
|
||||
51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000,
|
||||
51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel
|
||||
71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500,
|
||||
71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel
|
||||
_ => 1000, // need more fuel
|
||||
};
|
||||
let subset = cuda_filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
dev,
|
||||
funcs,
|
||||
)?;
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Vec<(&'a [f32], usize)>> {
|
||||
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let num_vectors = database.len();
|
||||
let num_dimensions = 250;
|
||||
let flattened_database: Vec<f32> = database.iter().flatten().cloned().collect();
|
||||
let database_dev = dev.htod_sync_copy(&flattened_database)?;
|
||||
let mean_query_dev = dev.htod_sync_copy(&mean_query_vector)?;
|
||||
let mut distances_dev = dev.alloc_zeros::<f32>(num_vectors)?;
|
||||
let cfg = LaunchConfig {
|
||||
block_dim: (256, 1, 1),
|
||||
grid_dim: ((num_vectors as u32 + 255) / 256, 1, 1),
|
||||
shared_mem_bytes: 0,
|
||||
};
|
||||
unsafe {
|
||||
funcs.remove("filter_vectors").unwrap().launch(
|
||||
cfg,
|
||||
(
|
||||
&mean_query_dev,
|
||||
&database_dev,
|
||||
&mut distances_dev,
|
||||
num_vectors as i32,
|
||||
num_dimensions as i32,
|
||||
),
|
||||
)
|
||||
}?;
|
||||
let mut distances_host = vec![0.0f32; num_vectors];
|
||||
dev.dtoh_sync_copy_into(&distances_dev, &mut distances_host)?;
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, &distance) in distances_host.iter().enumerate() {
|
||||
let ord_dist = FloatOrd(distance);
|
||||
if heap.len() < k {
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() {
|
||||
if distance < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&[f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_build_kd_tree<'a>(subset: &mut [(&'a [f32], usize)],
|
||||
dev: &Arc<CudaDevice>,
|
||||
funcs: &mut HashMap<&'static str, CudaFunction>,
|
||||
) -> Option<Box<KDNode<'a>>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_nearest_neighbor_search(
|
||||
kd_tree: &Option<Box<KDNode<'_>>>,
|
||||
query: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
dev: &Arc<CudaDevice>,
|
||||
funcs: &mut HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
468
tig-algorithms/src/vector_search/optimax_gpu/inbound.rs
Normal file
468
tig-algorithms/src/vector_search/optimax_gpu/inbound.rs
Normal file
@ -0,0 +1,468 @@
|
||||
/*!
|
||||
Copyright 2024 bw-dev36
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::Ok;
|
||||
use tig_challenges::vector_search::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
struct KDNode<'a> {
|
||||
point: &'a [f32],
|
||||
left: Option<Box<KDNode<'a>>>,
|
||||
right: Option<Box<KDNode<'a>>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> KDNode<'a> {
|
||||
fn new(point: &'a [f32], index: usize) -> Self {
|
||||
KDNode {
|
||||
point,
|
||||
left: None,
|
||||
right: None,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn quickselect_by<F>(arr: &mut [(&[f32], usize)], k: usize, compare: &F)
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
if arr.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let pivot_index = partition(arr, compare);
|
||||
if k < pivot_index {
|
||||
quickselect_by(&mut arr[..pivot_index], k, compare);
|
||||
} else if k > pivot_index {
|
||||
quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare);
|
||||
}
|
||||
}
|
||||
|
||||
fn partition<F>(arr: &mut [(&[f32], usize)], compare: &F) -> usize
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
let pivot_index = arr.len() >> 1;
|
||||
arr.swap(pivot_index, arr.len() - 1);
|
||||
|
||||
let mut store_index = 0;
|
||||
for i in 0..arr.len() - 1 {
|
||||
if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less {
|
||||
arr.swap(i, store_index);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
arr.swap(store_index, arr.len() - 1);
|
||||
store_index
|
||||
}
|
||||
|
||||
fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option<Box<KDNode<'a>>> {
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
const NUM_DIMENSIONS: usize = 250;
|
||||
let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new();
|
||||
let mut root: Option<Box<KDNode<'a>>> = None;
|
||||
|
||||
stack.push((0, points.len(), 0, None, false));
|
||||
|
||||
while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() {
|
||||
if start >= end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let axis = depth % NUM_DIMENSIONS;
|
||||
let median = (start + end) / 2;
|
||||
quickselect_by(&mut points[start..end], median - start, &|a, b| {
|
||||
a.0[axis].partial_cmp(&b.0[axis]).unwrap()
|
||||
});
|
||||
|
||||
let (median_point, median_index) = points[median];
|
||||
let mut new_node = Box::new(KDNode::new(median_point, median_index));
|
||||
let new_node_ptr: *mut KDNode = &mut *new_node;
|
||||
|
||||
if let Some(parent_ptr) = parent_ptr {
|
||||
unsafe {
|
||||
if is_left {
|
||||
(*parent_ptr).left = Some(new_node);
|
||||
} else {
|
||||
(*parent_ptr).right = Some(new_node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root = Some(new_node);
|
||||
}
|
||||
|
||||
stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false));
|
||||
stack.push((start, median, depth + 1, Some(new_node_ptr), true));
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..a.len() {
|
||||
let diff = a[i] - b[i];
|
||||
sum += diff * diff;
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
let mut i = 0;
|
||||
while i + 3 < a.len() {
|
||||
let diff0 = a[i] - b[i];
|
||||
let diff1 = a[i + 1] - b[i + 1];
|
||||
let diff2 = a[i + 2] - b[i + 2];
|
||||
let diff3 = a[i + 3] - b[i + 3];
|
||||
|
||||
sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3;
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 4;
|
||||
}
|
||||
|
||||
while i < a.len() {
|
||||
let diff = a[i] - b[i];
|
||||
sum += diff * diff;
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
sum
|
||||
}
|
||||
|
||||
fn nearest_neighbor_search<'a>(
|
||||
root: &Option<Box<KDNode<'a>>>,
|
||||
target: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
) {
|
||||
let num_dimensions = target.len();
|
||||
let mut stack = Vec::with_capacity(64);
|
||||
|
||||
if let Some(node) = root {
|
||||
stack.push((node.as_ref(), 0));
|
||||
}
|
||||
|
||||
while let Some((node, depth)) = stack.pop() {
|
||||
let axis = depth % num_dimensions;
|
||||
let dist = early_stopping_distance(&node.point, target, best.0);
|
||||
|
||||
if dist < best.0 {
|
||||
best.0 = dist;
|
||||
best.1 = Some(node.index);
|
||||
}
|
||||
|
||||
let diff = target[axis] - node.point[axis];
|
||||
let sqr_diff = diff * diff;
|
||||
|
||||
if sqr_diff < best.0 {
|
||||
if let Some(farther_node) = if diff < 0.0 { &node.right } else { &node.left } {
|
||||
stack.push((farther_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(nearer_node) = if diff < 0.0 { &node.left } else { &node.right } {
|
||||
stack.push((nearer_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec<f32> {
|
||||
let num_vectors = vectors.len();
|
||||
let num_dimensions = 250;
|
||||
|
||||
let mut mean_vector = vec![0.0; num_dimensions];
|
||||
|
||||
for vector in vectors {
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] += vector[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] /= num_vectors as f32;
|
||||
}
|
||||
|
||||
mean_vector
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FloatOrd(f32);
|
||||
|
||||
impl PartialEq for FloatOrd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FloatOrd {}
|
||||
|
||||
impl PartialOrd for FloatOrd {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FloatOrd {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
|
||||
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
) -> Vec<(&'a [f32], usize)> {
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, vector) in database.iter().enumerate() {
|
||||
let dist = squared_euclidean_distance(&mean_query_vector, vector);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
if heap.len() < k {
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() {
|
||||
if dist < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&'a [f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let subset_size = match query_count {
|
||||
10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200,
|
||||
10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200,
|
||||
20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000,
|
||||
20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel
|
||||
29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000,
|
||||
29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000,
|
||||
46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel
|
||||
51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000,
|
||||
51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel
|
||||
71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500,
|
||||
71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel
|
||||
_ => 1000, // need more fuel
|
||||
};
|
||||
let subset = filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
);
|
||||
|
||||
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
pub const KERNEL: Option<CudaKernel> = Some(CudaKernel {
|
||||
src: r#"
|
||||
|
||||
extern "C" __global__ void filter_vectors(float* query_mean, float* vectors, float* distances, int num_vectors, int num_dimensions) {
|
||||
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (idx < num_vectors) {
|
||||
float dist = 0.0;
|
||||
for (int d = 0; d < num_dimensions; ++d) {
|
||||
float diff = query_mean[d] - vectors[idx * num_dimensions + d];
|
||||
dist += diff * diff;
|
||||
}
|
||||
distances[idx] = dist;
|
||||
}
|
||||
}
|
||||
|
||||
"#,
|
||||
|
||||
funcs: &["filter_vectors"],
|
||||
});
|
||||
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let subset_size = match query_count {
|
||||
10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200,
|
||||
10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200,
|
||||
20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000,
|
||||
20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel
|
||||
29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000,
|
||||
29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000,
|
||||
46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel
|
||||
51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000,
|
||||
51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel
|
||||
71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500,
|
||||
71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel
|
||||
_ => 1000, // need more fuel
|
||||
};
|
||||
let subset = cuda_filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
dev,
|
||||
funcs,
|
||||
)?;
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Vec<(&'a [f32], usize)>> {
|
||||
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let num_vectors = database.len();
|
||||
let num_dimensions = 250;
|
||||
let flattened_database: Vec<f32> = database.iter().flatten().cloned().collect();
|
||||
let database_dev = dev.htod_sync_copy(&flattened_database)?;
|
||||
let mean_query_dev = dev.htod_sync_copy(&mean_query_vector)?;
|
||||
let mut distances_dev = dev.alloc_zeros::<f32>(num_vectors)?;
|
||||
let cfg = LaunchConfig {
|
||||
block_dim: (256, 1, 1),
|
||||
grid_dim: ((num_vectors as u32 + 255) / 256, 1, 1),
|
||||
shared_mem_bytes: 0,
|
||||
};
|
||||
unsafe {
|
||||
funcs.remove("filter_vectors").unwrap().launch(
|
||||
cfg,
|
||||
(
|
||||
&mean_query_dev,
|
||||
&database_dev,
|
||||
&mut distances_dev,
|
||||
num_vectors as i32,
|
||||
num_dimensions as i32,
|
||||
),
|
||||
)
|
||||
}?;
|
||||
let mut distances_host = vec![0.0f32; num_vectors];
|
||||
dev.dtoh_sync_copy_into(&distances_dev, &mut distances_host)?;
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, &distance) in distances_host.iter().enumerate() {
|
||||
let ord_dist = FloatOrd(distance);
|
||||
if heap.len() < k {
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() {
|
||||
if distance < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&[f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_build_kd_tree<'a>(subset: &mut [(&'a [f32], usize)],
|
||||
dev: &Arc<CudaDevice>,
|
||||
funcs: &mut HashMap<&'static str, CudaFunction>,
|
||||
) -> Option<Box<KDNode<'a>>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_nearest_neighbor_search(
|
||||
kd_tree: &Option<Box<KDNode<'_>>>,
|
||||
query: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
dev: &Arc<CudaDevice>,
|
||||
funcs: &mut HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -0,0 +1,468 @@
|
||||
/*!
|
||||
Copyright 2024 bw-dev36
|
||||
|
||||
Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you
|
||||
may not use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::Ok;
|
||||
use tig_challenges::vector_search::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
struct KDNode<'a> {
|
||||
point: &'a [f32],
|
||||
left: Option<Box<KDNode<'a>>>,
|
||||
right: Option<Box<KDNode<'a>>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> KDNode<'a> {
|
||||
fn new(point: &'a [f32], index: usize) -> Self {
|
||||
KDNode {
|
||||
point,
|
||||
left: None,
|
||||
right: None,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn quickselect_by<F>(arr: &mut [(&[f32], usize)], k: usize, compare: &F)
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
if arr.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let pivot_index = partition(arr, compare);
|
||||
if k < pivot_index {
|
||||
quickselect_by(&mut arr[..pivot_index], k, compare);
|
||||
} else if k > pivot_index {
|
||||
quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare);
|
||||
}
|
||||
}
|
||||
|
||||
fn partition<F>(arr: &mut [(&[f32], usize)], compare: &F) -> usize
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
let pivot_index = arr.len() >> 1;
|
||||
arr.swap(pivot_index, arr.len() - 1);
|
||||
|
||||
let mut store_index = 0;
|
||||
for i in 0..arr.len() - 1 {
|
||||
if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less {
|
||||
arr.swap(i, store_index);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
arr.swap(store_index, arr.len() - 1);
|
||||
store_index
|
||||
}
|
||||
|
||||
fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option<Box<KDNode<'a>>> {
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
const NUM_DIMENSIONS: usize = 250;
|
||||
let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new();
|
||||
let mut root: Option<Box<KDNode<'a>>> = None;
|
||||
|
||||
stack.push((0, points.len(), 0, None, false));
|
||||
|
||||
while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() {
|
||||
if start >= end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let axis = depth % NUM_DIMENSIONS;
|
||||
let median = (start + end) / 2;
|
||||
quickselect_by(&mut points[start..end], median - start, &|a, b| {
|
||||
a.0[axis].partial_cmp(&b.0[axis]).unwrap()
|
||||
});
|
||||
|
||||
let (median_point, median_index) = points[median];
|
||||
let mut new_node = Box::new(KDNode::new(median_point, median_index));
|
||||
let new_node_ptr: *mut KDNode = &mut *new_node;
|
||||
|
||||
if let Some(parent_ptr) = parent_ptr {
|
||||
unsafe {
|
||||
if is_left {
|
||||
(*parent_ptr).left = Some(new_node);
|
||||
} else {
|
||||
(*parent_ptr).right = Some(new_node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root = Some(new_node);
|
||||
}
|
||||
|
||||
stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false));
|
||||
stack.push((start, median, depth + 1, Some(new_node_ptr), true));
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..a.len() {
|
||||
let diff = a[i] - b[i];
|
||||
sum += diff * diff;
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
let mut i = 0;
|
||||
while i + 3 < a.len() {
|
||||
let diff0 = a[i] - b[i];
|
||||
let diff1 = a[i + 1] - b[i + 1];
|
||||
let diff2 = a[i + 2] - b[i + 2];
|
||||
let diff3 = a[i + 3] - b[i + 3];
|
||||
|
||||
sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3;
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 4;
|
||||
}
|
||||
|
||||
while i < a.len() {
|
||||
let diff = a[i] - b[i];
|
||||
sum += diff * diff;
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
sum
|
||||
}
|
||||
|
||||
fn nearest_neighbor_search<'a>(
|
||||
root: &Option<Box<KDNode<'a>>>,
|
||||
target: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
) {
|
||||
let num_dimensions = target.len();
|
||||
let mut stack = Vec::with_capacity(64);
|
||||
|
||||
if let Some(node) = root {
|
||||
stack.push((node.as_ref(), 0));
|
||||
}
|
||||
|
||||
while let Some((node, depth)) = stack.pop() {
|
||||
let axis = depth % num_dimensions;
|
||||
let dist = early_stopping_distance(&node.point, target, best.0);
|
||||
|
||||
if dist < best.0 {
|
||||
best.0 = dist;
|
||||
best.1 = Some(node.index);
|
||||
}
|
||||
|
||||
let diff = target[axis] - node.point[axis];
|
||||
let sqr_diff = diff * diff;
|
||||
|
||||
if sqr_diff < best.0 {
|
||||
if let Some(farther_node) = if diff < 0.0 { &node.right } else { &node.left } {
|
||||
stack.push((farther_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(nearer_node) = if diff < 0.0 { &node.left } else { &node.right } {
|
||||
stack.push((nearer_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec<f32> {
|
||||
let num_vectors = vectors.len();
|
||||
let num_dimensions = 250;
|
||||
|
||||
let mut mean_vector = vec![0.0; num_dimensions];
|
||||
|
||||
for vector in vectors {
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] += vector[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] /= num_vectors as f32;
|
||||
}
|
||||
|
||||
mean_vector
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FloatOrd(f32);
|
||||
|
||||
impl PartialEq for FloatOrd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FloatOrd {}
|
||||
|
||||
impl PartialOrd for FloatOrd {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FloatOrd {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
|
||||
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
) -> Vec<(&'a [f32], usize)> {
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, vector) in database.iter().enumerate() {
|
||||
let dist = squared_euclidean_distance(&mean_query_vector, vector);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
if heap.len() < k {
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() {
|
||||
if dist < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&'a [f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let subset_size = match query_count {
|
||||
10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200,
|
||||
10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200,
|
||||
20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000,
|
||||
20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel
|
||||
29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000,
|
||||
29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000,
|
||||
46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel
|
||||
51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000,
|
||||
51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel
|
||||
71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500,
|
||||
71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel
|
||||
_ => 1000, // need more fuel
|
||||
};
|
||||
let subset = filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
);
|
||||
|
||||
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
pub const KERNEL: Option<CudaKernel> = Some(CudaKernel {
|
||||
src: r#"
|
||||
|
||||
extern "C" __global__ void filter_vectors(float* query_mean, float* vectors, float* distances, int num_vectors, int num_dimensions) {
|
||||
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (idx < num_vectors) {
|
||||
float dist = 0.0;
|
||||
for (int d = 0; d < num_dimensions; ++d) {
|
||||
float diff = query_mean[d] - vectors[idx * num_dimensions + d];
|
||||
dist += diff * diff;
|
||||
}
|
||||
distances[idx] = dist;
|
||||
}
|
||||
}
|
||||
|
||||
"#,
|
||||
|
||||
funcs: &["filter_vectors"],
|
||||
});
|
||||
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let subset_size = match query_count {
|
||||
10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200,
|
||||
10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200,
|
||||
20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000,
|
||||
20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel
|
||||
29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000,
|
||||
29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000,
|
||||
46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel
|
||||
51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000,
|
||||
51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel
|
||||
71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500,
|
||||
71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel
|
||||
_ => 1000, // need more fuel
|
||||
};
|
||||
let subset = cuda_filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
dev,
|
||||
funcs,
|
||||
)?;
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Vec<(&'a [f32], usize)>> {
|
||||
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let num_vectors = database.len();
|
||||
let num_dimensions = 250;
|
||||
let flattened_database: Vec<f32> = database.iter().flatten().cloned().collect();
|
||||
let database_dev = dev.htod_sync_copy(&flattened_database)?;
|
||||
let mean_query_dev = dev.htod_sync_copy(&mean_query_vector)?;
|
||||
let mut distances_dev = dev.alloc_zeros::<f32>(num_vectors)?;
|
||||
let cfg = LaunchConfig {
|
||||
block_dim: (256, 1, 1),
|
||||
grid_dim: ((num_vectors as u32 + 255) / 256, 1, 1),
|
||||
shared_mem_bytes: 0,
|
||||
};
|
||||
unsafe {
|
||||
funcs.remove("filter_vectors").unwrap().launch(
|
||||
cfg,
|
||||
(
|
||||
&mean_query_dev,
|
||||
&database_dev,
|
||||
&mut distances_dev,
|
||||
num_vectors as i32,
|
||||
num_dimensions as i32,
|
||||
),
|
||||
)
|
||||
}?;
|
||||
let mut distances_host = vec![0.0f32; num_vectors];
|
||||
dev.dtoh_sync_copy_into(&distances_dev, &mut distances_host)?;
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, &distance) in distances_host.iter().enumerate() {
|
||||
let ord_dist = FloatOrd(distance);
|
||||
if heap.len() < k {
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() {
|
||||
if distance < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&[f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_build_kd_tree<'a>(subset: &mut [(&'a [f32], usize)],
|
||||
dev: &Arc<CudaDevice>,
|
||||
funcs: &mut HashMap<&'static str, CudaFunction>,
|
||||
) -> Option<Box<KDNode<'a>>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_nearest_neighbor_search(
|
||||
kd_tree: &Option<Box<KDNode<'_>>>,
|
||||
query: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
dev: &Arc<CudaDevice>,
|
||||
funcs: &mut HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
4
tig-algorithms/src/vector_search/optimax_gpu/mod.rs
Normal file
4
tig-algorithms/src/vector_search/optimax_gpu/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod benchmarker_outbound;
|
||||
pub use benchmarker_outbound::solve_challenge;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL};
|
||||
468
tig-algorithms/src/vector_search/optimax_gpu/open_data.rs
Normal file
468
tig-algorithms/src/vector_search/optimax_gpu/open_data.rs
Normal file
@ -0,0 +1,468 @@
|
||||
/*!
|
||||
Copyright 2024 bw-dev36
|
||||
|
||||
Licensed under the TIG Open Data License v1.0 or (at your option) any later version
|
||||
(the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::Ok;
|
||||
use tig_challenges::vector_search::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
struct KDNode<'a> {
|
||||
point: &'a [f32],
|
||||
left: Option<Box<KDNode<'a>>>,
|
||||
right: Option<Box<KDNode<'a>>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> KDNode<'a> {
|
||||
fn new(point: &'a [f32], index: usize) -> Self {
|
||||
KDNode {
|
||||
point,
|
||||
left: None,
|
||||
right: None,
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn quickselect_by<F>(arr: &mut [(&[f32], usize)], k: usize, compare: &F)
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
if arr.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let pivot_index = partition(arr, compare);
|
||||
if k < pivot_index {
|
||||
quickselect_by(&mut arr[..pivot_index], k, compare);
|
||||
} else if k > pivot_index {
|
||||
quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare);
|
||||
}
|
||||
}
|
||||
|
||||
fn partition<F>(arr: &mut [(&[f32], usize)], compare: &F) -> usize
|
||||
where
|
||||
F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering,
|
||||
{
|
||||
let pivot_index = arr.len() >> 1;
|
||||
arr.swap(pivot_index, arr.len() - 1);
|
||||
|
||||
let mut store_index = 0;
|
||||
for i in 0..arr.len() - 1 {
|
||||
if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less {
|
||||
arr.swap(i, store_index);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
arr.swap(store_index, arr.len() - 1);
|
||||
store_index
|
||||
}
|
||||
|
||||
fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option<Box<KDNode<'a>>> {
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
const NUM_DIMENSIONS: usize = 250;
|
||||
let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new();
|
||||
let mut root: Option<Box<KDNode<'a>>> = None;
|
||||
|
||||
stack.push((0, points.len(), 0, None, false));
|
||||
|
||||
while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() {
|
||||
if start >= end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let axis = depth % NUM_DIMENSIONS;
|
||||
let median = (start + end) / 2;
|
||||
quickselect_by(&mut points[start..end], median - start, &|a, b| {
|
||||
a.0[axis].partial_cmp(&b.0[axis]).unwrap()
|
||||
});
|
||||
|
||||
let (median_point, median_index) = points[median];
|
||||
let mut new_node = Box::new(KDNode::new(median_point, median_index));
|
||||
let new_node_ptr: *mut KDNode = &mut *new_node;
|
||||
|
||||
if let Some(parent_ptr) = parent_ptr {
|
||||
unsafe {
|
||||
if is_left {
|
||||
(*parent_ptr).left = Some(new_node);
|
||||
} else {
|
||||
(*parent_ptr).right = Some(new_node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root = Some(new_node);
|
||||
}
|
||||
|
||||
stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false));
|
||||
stack.push((start, median, depth + 1, Some(new_node_ptr), true));
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..a.len() {
|
||||
let diff = a[i] - b[i];
|
||||
sum += diff * diff;
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 {
|
||||
let mut sum = 0.0;
|
||||
let mut i = 0;
|
||||
while i + 3 < a.len() {
|
||||
let diff0 = a[i] - b[i];
|
||||
let diff1 = a[i + 1] - b[i + 1];
|
||||
let diff2 = a[i + 2] - b[i + 2];
|
||||
let diff3 = a[i + 3] - b[i + 3];
|
||||
|
||||
sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3;
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 4;
|
||||
}
|
||||
|
||||
while i < a.len() {
|
||||
let diff = a[i] - b[i];
|
||||
sum += diff * diff;
|
||||
|
||||
if sum > current_min {
|
||||
return f32::MAX;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
sum
|
||||
}
|
||||
|
||||
fn nearest_neighbor_search<'a>(
|
||||
root: &Option<Box<KDNode<'a>>>,
|
||||
target: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
) {
|
||||
let num_dimensions = target.len();
|
||||
let mut stack = Vec::with_capacity(64);
|
||||
|
||||
if let Some(node) = root {
|
||||
stack.push((node.as_ref(), 0));
|
||||
}
|
||||
|
||||
while let Some((node, depth)) = stack.pop() {
|
||||
let axis = depth % num_dimensions;
|
||||
let dist = early_stopping_distance(&node.point, target, best.0);
|
||||
|
||||
if dist < best.0 {
|
||||
best.0 = dist;
|
||||
best.1 = Some(node.index);
|
||||
}
|
||||
|
||||
let diff = target[axis] - node.point[axis];
|
||||
let sqr_diff = diff * diff;
|
||||
|
||||
if sqr_diff < best.0 {
|
||||
if let Some(farther_node) = if diff < 0.0 { &node.right } else { &node.left } {
|
||||
stack.push((farther_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(nearer_node) = if diff < 0.0 { &node.left } else { &node.right } {
|
||||
stack.push((nearer_node.as_ref(), depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec<f32> {
|
||||
let num_vectors = vectors.len();
|
||||
let num_dimensions = 250;
|
||||
|
||||
let mut mean_vector = vec![0.0; num_dimensions];
|
||||
|
||||
for vector in vectors {
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] += vector[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..num_dimensions {
|
||||
mean_vector[i] /= num_vectors as f32;
|
||||
}
|
||||
|
||||
mean_vector
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FloatOrd(f32);
|
||||
|
||||
impl PartialEq for FloatOrd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FloatOrd {}
|
||||
|
||||
impl PartialOrd for FloatOrd {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FloatOrd {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
|
||||
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
) -> Vec<(&'a [f32], usize)> {
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, vector) in database.iter().enumerate() {
|
||||
let dist = squared_euclidean_distance(&mean_query_vector, vector);
|
||||
let ord_dist = FloatOrd(dist);
|
||||
if heap.len() < k {
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() {
|
||||
if dist < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&'a [f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let subset_size = match query_count {
|
||||
10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200,
|
||||
10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200,
|
||||
20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000,
|
||||
20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel
|
||||
29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000,
|
||||
29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000,
|
||||
46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel
|
||||
51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000,
|
||||
51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel
|
||||
71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500,
|
||||
71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel
|
||||
_ => 1000, // need more fuel
|
||||
};
|
||||
let subset = filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
);
|
||||
|
||||
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod gpu_optimisation {
|
||||
use super::*;
|
||||
use cudarc::driver::*;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tig_challenges::CudaKernel;
|
||||
pub const KERNEL: Option<CudaKernel> = Some(CudaKernel {
|
||||
src: r#"
|
||||
|
||||
extern "C" __global__ void filter_vectors(float* query_mean, float* vectors, float* distances, int num_vectors, int num_dimensions) {
|
||||
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (idx < num_vectors) {
|
||||
float dist = 0.0;
|
||||
for (int d = 0; d < num_dimensions; ++d) {
|
||||
float diff = query_mean[d] - vectors[idx * num_dimensions + d];
|
||||
dist += diff * diff;
|
||||
}
|
||||
distances[idx] = dist;
|
||||
}
|
||||
}
|
||||
|
||||
"#,
|
||||
|
||||
funcs: &["filter_vectors"],
|
||||
});
|
||||
|
||||
pub fn cuda_solve_challenge(
|
||||
challenge: &Challenge,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Option<Solution>> {
|
||||
let query_count = challenge.query_vectors.len();
|
||||
|
||||
let subset_size = match query_count {
|
||||
10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200,
|
||||
10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200,
|
||||
20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000,
|
||||
20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel
|
||||
29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000,
|
||||
29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000,
|
||||
46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel
|
||||
51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000,
|
||||
51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel
|
||||
71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500,
|
||||
71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel
|
||||
_ => 1000, // need more fuel
|
||||
};
|
||||
let subset = cuda_filter_relevant_vectors(
|
||||
&challenge.vector_database,
|
||||
&challenge.query_vectors,
|
||||
subset_size,
|
||||
dev,
|
||||
funcs,
|
||||
)?;
|
||||
let kd_tree = build_kd_tree(&mut subset.clone());
|
||||
|
||||
|
||||
let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len());
|
||||
|
||||
for query in challenge.query_vectors.iter() {
|
||||
let mut best = (std::f32::MAX, None);
|
||||
nearest_neighbor_search(&kd_tree, query, &mut best);
|
||||
|
||||
if let Some(best_index) = best.1 {
|
||||
best_indexes.push(best_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ok(Some(Solution {
|
||||
indexes: best_indexes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_filter_relevant_vectors<'a>(
|
||||
database: &'a [Vec<f32>],
|
||||
query_vectors: &[Vec<f32>],
|
||||
k: usize,
|
||||
dev: &Arc<CudaDevice>,
|
||||
mut funcs: HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<Vec<(&'a [f32], usize)>> {
|
||||
|
||||
let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect();
|
||||
let mean_query_vector = calculate_mean_vector(&query_refs);
|
||||
|
||||
let num_vectors = database.len();
|
||||
let num_dimensions = 250;
|
||||
let flattened_database: Vec<f32> = database.iter().flatten().cloned().collect();
|
||||
let database_dev = dev.htod_sync_copy(&flattened_database)?;
|
||||
let mean_query_dev = dev.htod_sync_copy(&mean_query_vector)?;
|
||||
let mut distances_dev = dev.alloc_zeros::<f32>(num_vectors)?;
|
||||
let cfg = LaunchConfig {
|
||||
block_dim: (256, 1, 1),
|
||||
grid_dim: ((num_vectors as u32 + 255) / 256, 1, 1),
|
||||
shared_mem_bytes: 0,
|
||||
};
|
||||
unsafe {
|
||||
funcs.remove("filter_vectors").unwrap().launch(
|
||||
cfg,
|
||||
(
|
||||
&mean_query_dev,
|
||||
&database_dev,
|
||||
&mut distances_dev,
|
||||
num_vectors as i32,
|
||||
num_dimensions as i32,
|
||||
),
|
||||
)
|
||||
}?;
|
||||
let mut distances_host = vec![0.0f32; num_vectors];
|
||||
dev.dtoh_sync_copy_into(&distances_dev, &mut distances_host)?;
|
||||
let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k);
|
||||
|
||||
for (index, &distance) in distances_host.iter().enumerate() {
|
||||
let ord_dist = FloatOrd(distance);
|
||||
if heap.len() < k {
|
||||
heap.push((ord_dist, index));
|
||||
} else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() {
|
||||
if distance < top_dist {
|
||||
heap.pop();
|
||||
heap.push((ord_dist, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
let result: Vec<(&[f32], usize)> = heap
|
||||
.into_iter()
|
||||
.map(|(_, index)| (&database[index][..], index))
|
||||
.collect();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_build_kd_tree<'a>(subset: &mut [(&'a [f32], usize)],
|
||||
dev: &Arc<CudaDevice>,
|
||||
funcs: &mut HashMap<&'static str, CudaFunction>,
|
||||
) -> Option<Box<KDNode<'a>>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn cuda_nearest_neighbor_search(
|
||||
kd_tree: &Option<Box<KDNode<'_>>>,
|
||||
query: &[f32],
|
||||
best: &mut (f32, Option<usize>),
|
||||
dev: &Arc<CudaDevice>,
|
||||
funcs: &mut HashMap<&'static str, CudaFunction>,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use gpu_optimisation::{cuda_solve_challenge, KERNEL};
|
||||
@ -1,7 +1,11 @@
|
||||
/*!
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright [year copyright work created] [name of copyright owner]
|
||||
|
||||
Licensed under the TIG Inbound Game License v1.0 or (at your option) any later
|
||||
Identity of Submitter [name of person or entity that submits the Work to TIG]
|
||||
|
||||
UAI [UAI (if applicable)]
|
||||
|
||||
Licensed under the TIG Inbound Game License v2.0 or (at your option) any later
|
||||
version (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
|
||||
@ -13,6 +17,24 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci
|
||||
language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// REMOVE BELOW SECTION IF UNUSED
|
||||
/*
|
||||
REFERENCES AND ACKNOWLEDGMENTS
|
||||
|
||||
This implementation is based on or inspired by existing work. Citations and
|
||||
acknowledgments below:
|
||||
|
||||
1. Academic Papers:
|
||||
- [Author(s), "Paper Title", DOI (if available)]
|
||||
|
||||
2. Code References:
|
||||
- [Author(s), URL]
|
||||
|
||||
3. Other:
|
||||
- [Author(s), Details]
|
||||
|
||||
*/
|
||||
|
||||
// TIG's UI uses the pattern `tig_challenges::<challenge_name>` to automatically detect your algorithm's challenge
|
||||
use anyhow::{anyhow, Result};
|
||||
use tig_challenges::vector_search::{Challenge, Solution};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user