pldag 3.0.2

A DAG-based combinatorial-model framework with optional GLPK solving back-end.
Documentation

Primitive Logic Directed Acyclic Graph (PL‑DAG)

A Primitive Logic Directed Acyclic Graph (PL‑DAG) is a DAG in which every node encodes a logical operation and every leaf represents a literal. Interior nodes freely express arbitrary Boolean combinations of their predecessors—for example, an AND‑node evaluates to true only if all of its incoming nodes (or leaves) evaluate to true. This flexibility makes the PL‑DAG both powerful and easy to work with.


✨ Key Features

Area What you get
Modelling Build Boolean/linear constraint systems in a single graph representation.
Analysis Fast bound‑propagation (propagate*) and coefficient accumulation (propagate_coefs*).
Export to_sparse_polyhedron() generates a polyhedral ILP model ready for any solver.
🧩 Optional solver Turn on the glpk feature to link against GLPK and solve in‑process.

Install

1  — Modelling‑only (MIT licence)

cargo add pldag

This pulls no GPL code; you can ship the resulting binary under any licence compatible with MIT.

2  — Modelling + in‑process GLPK solver (GPL v3+ applies)

cargo add pldag --features glpk

Enabling the glpk feature links to the GNU Linear Programming Kit (GLPK). If you distribute a binary built with this feature you must meet the requirements of the GPL‑3.0‑or‑later.

Heads‑up: Leaving the feature off keeps all code MIT‑licensed. The choice is completely under your control at cargo build time.


Core Routines

1. propagate

fn propagate(
    &self,
    assignment: &IndexMap<&str, Bound>, // Assignment = IndexMap<String, Bound>
) -> Assignment;

Propagates bounds bottom‑up through the DAG and returns a map of node → bound ((min, max)).

2. propagate_coefs

fn propagate_coefs(
    &self,
    assignment: &IndexMap<&str, Bound>,
) -> ValuedAssignment; // IndexMap<String, MultiBound>

Propagates both bounds and linear coefficients (MultiBound = (Bound, VBound)).

3. Convenience variants

fn propagate_default(&self) -> Assignment;
fn propagate_coefs_default(&self) -> ValuedAssignment;

4. to_sparse_polyhedron

fn to_sparse_polyhedron(
    &self,
    double_binding: bool,
    integer_constraints: bool,
    fixed_constraints: bool,
) -> SparsePolyhedron;

Emits a sparse polyhedral representation suitable for ILP solvers (GLPK, CPLEX, Gurobi, …). SparsePolyhedron implements serde::Serialize, so you can also ship it over HTTP to a remote solver service if you prefer.

5. Node management helpers

fn set_coef(&mut self, id: &str, coefficient: f64);
fn get_coef(&self, id: &str) -> f64;
fn get_objective(&self) -> IndexMap<String, f64>;
fn set_primitive(&mut self, id: &str, bound: Bound);

6. solve (Optional GLPK Feature)

#[cfg(feature = "glpk")]
fn solve(
    &self,
    objectives: Vec<HashMap<&str, f64>>,
    assume: HashMap<&str, Bound>,
    maximize: bool,
) -> Vec<Option<Assignment>>;

Solves integer linear programming problems using GLPK. Takes multiple objective functions, fixed variable assumptions, and returns optimal assignments.


Quick Example

use indexmap::IndexMap;
use pldag::{Pldag, Bound};

// Build a simple OR‑of‑three model
let mut pldag = Pldag::new();
pldag.set_primitive("x", (0, 1));
pldag.set_primitive("y", (0, 1));
pldag.set_primitive("z", (0, 1));
let root = pldag.set_or(vec!["x", "y", "z"]);

// 1. Validate a combination
let validated = pldag.propagate_default();
println!("root bound = {:?}", validated[&root]);

// 2. Optimise with coefficients
pldag.set_coef("x", 1.0);
pldag.set_coef("y", 2.0);
pldag.set_coef("z", 3.0);
pldag.set_coef(&root, -1.0);
let scored = pldag.propagate_coefs_default();
println!("root value = {:?}", scored[&root].1);

3. Solving with GLPK (Optional Feature)

When the glpk feature is enabled, you can solve optimization problems directly:

#[cfg(feature = "glpk")]
use std::collections::HashMap;
use pldag::{Pldag, Bound};

// Build a simple problem: maximize x + 2y + 3z subject to x ∨ y ∨ z
let mut pldag = Pldag::new();
pldag.set_primitive("x", (0, 1));
pldag.set_primitive("y", (0, 1)); 
pldag.set_primitive("z", (0, 1));
let root = pldag.set_or(vec!["x", "y", "z"]);

// Set up the objective function: maximize x + 2y + 3z
let mut objective = HashMap::new();
objective.insert("x", 1.0);
objective.insert("y", 2.0);
objective.insert("z", 3.0);

// Constraints: require that the OR constraint is satisfied
let mut assumptions = HashMap::new();
assumptions.insert(&root, (1, 1)); // root must be true

// Solve the optimization problem
let solutions = pldag.solve(vec![objective], assumptions, true);

if let Some(solution) = &solutions[0] {
    println!("Optimal solution found:");
    println!("x = {:?}", solution.get("x"));
    println!("y = {:?}", solution.get("y"));
    println!("z = {:?}", solution.get("z"));
    println!("root = {:?}", solution.get(&root));
} else {
    println!("No feasible solution found");
}

This example demonstrates:

  • Problem setup: Creating boolean variables and logical constraints
  • Objective function: Defining what to optimize (maximize x + 2y + 3z)
  • Assumptions: Fixing certain variables or constraints (root must be true)
  • Solving: Using GLPK to find the optimal solution
  • Result interpretation: Extracting variable values from the solution

License

  • Library code: MIT (permissive).
  • Optional solver: If you build with --features glpk, you link against GLPK, which is GPL‑3.0‑or‑later. Distributing such a binary triggers the GPL’s obligations.

You choose the trade‑off: leave the feature off for a fully permissive dependency tree, or enable it for a batteries‑included ILP solver.


Enjoy building and evaluating logical models with PL‑DAG! 🎉