#[allow(unused_imports)]
use crate::prelude::*;
use num_bigint::BigInt;
use num_rational::BigRational;
use num_traits::{One, Signed, Zero};
pub type VarId = usize;
pub type ConstraintId = usize;
#[derive(Debug, Clone)]
pub struct LinearConstraint {
pub coeffs: FxHashMap<VarId, BigRational>,
pub rhs: BigRational,
}
#[derive(Debug, Clone)]
pub struct FarkasCertificate {
pub multipliers: FxHashMap<ConstraintId, BigRational>,
}
impl FarkasCertificate {
pub fn new() -> Self {
Self {
multipliers: FxHashMap::default(),
}
}
pub fn add_multiplier(&mut self, constraint_id: ConstraintId, multiplier: BigRational) {
assert!(
multiplier >= BigRational::zero(),
"Farkas multipliers must be non-negative"
);
self.multipliers.insert(constraint_id, multiplier);
}
pub fn is_valid(&self, constraints: &[LinearConstraint]) -> bool {
let mut combined_coeffs: FxHashMap<VarId, BigRational> = FxHashMap::default();
for (&cid, multiplier) in &self.multipliers {
if let Some(constraint) = constraints.get(cid) {
for (&var, coeff) in &constraint.coeffs {
*combined_coeffs.entry(var).or_insert_with(BigRational::zero) +=
multiplier.clone() * coeff;
}
}
}
for coeff in combined_coeffs.values() {
if coeff.abs() > BigRational::new(BigInt::from(1), BigInt::from(1000000)) {
return false;
}
}
let mut combined_rhs = BigRational::zero();
for (&cid, multiplier) in &self.multipliers {
if let Some(constraint) = constraints.get(cid) {
combined_rhs += multiplier.clone() * constraint.rhs.clone();
}
}
combined_rhs < BigRational::zero()
}
}
impl Default for FarkasCertificate {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct FarkasConfig {
pub validate_certificate: bool,
pub minimize_certificate: bool,
}
impl Default for FarkasConfig {
fn default() -> Self {
Self {
validate_certificate: true,
minimize_certificate: false,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct FarkasStats {
pub certificates_generated: u64,
pub validations: u64,
pub invalid_certificates: u64,
}
#[derive(Debug)]
pub struct FarkasGenerator {
config: FarkasConfig,
stats: FarkasStats,
}
impl FarkasGenerator {
pub fn new(config: FarkasConfig) -> Self {
Self {
config,
stats: FarkasStats::default(),
}
}
pub fn default_config() -> Self {
Self::new(FarkasConfig::default())
}
pub fn generate_from_dual(
&mut self,
_constraints: &[LinearConstraint],
_dual_solution: &FxHashMap<ConstraintId, BigRational>,
) -> Option<FarkasCertificate> {
self.stats.certificates_generated += 1;
None
}
pub fn generate_from_core(
&mut self,
constraints: &[LinearConstraint],
core: &[ConstraintId],
) -> Option<FarkasCertificate> {
self.stats.certificates_generated += 1;
let mut certificate = FarkasCertificate::new();
let weight = BigRational::one();
for &cid in core {
certificate.add_multiplier(cid, weight.clone());
}
if self.config.validate_certificate && !self.validate(&certificate, constraints) {
return None;
}
Some(certificate)
}
pub fn validate(
&mut self,
certificate: &FarkasCertificate,
constraints: &[LinearConstraint],
) -> bool {
self.stats.validations += 1;
let valid = certificate.is_valid(constraints);
if !valid {
self.stats.invalid_certificates += 1;
}
valid
}
pub fn minimize(&mut self, _certificate: &mut FarkasCertificate) {
if !self.config.minimize_certificate {}
}
pub fn stats(&self) -> &FarkasStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = FarkasStats::default();
}
}
impl Default for FarkasGenerator {
fn default() -> Self {
Self::default_config()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generator_creation() {
let generator = FarkasGenerator::default_config();
assert_eq!(generator.stats().certificates_generated, 0);
}
#[test]
fn test_certificate_creation() {
let mut cert = FarkasCertificate::new();
cert.add_multiplier(0, BigRational::one());
cert.add_multiplier(1, BigRational::from(BigInt::from(2)));
assert_eq!(cert.multipliers.len(), 2);
}
#[test]
#[should_panic(expected = "Farkas multipliers must be non-negative")]
fn test_negative_multiplier_panics() {
let mut cert = FarkasCertificate::new();
cert.add_multiplier(0, BigRational::from(BigInt::from(-1)));
}
#[test]
fn test_generate_from_core() {
let mut generator = FarkasGenerator::default_config();
let mut constraints = Vec::new();
let mut coeffs = FxHashMap::default();
coeffs.insert(0, BigRational::one());
constraints.push(LinearConstraint {
coeffs,
rhs: BigRational::zero(),
});
let core = vec![0];
let _cert = generator.generate_from_core(&constraints, &core);
assert_eq!(generator.stats().certificates_generated, 1);
}
#[test]
fn test_stats() {
let mut generator = FarkasGenerator::default_config();
generator.stats.certificates_generated = 5;
assert_eq!(generator.stats().certificates_generated, 5);
generator.reset_stats();
assert_eq!(generator.stats().certificates_generated, 0);
}
}