Skip to content

Commit

Permalink
feat!: add Submodel
Browse files Browse the repository at this point in the history
Add `Submodel`, and refactor `Model` to be a wrapper over `Submodel`.

This commit is part of on-going work to add lexical scope to Conjure
Oxide and is a follow up to 8636432 (feat!: add parent symbol tables
(#680), 2025-02-17).

DETAILS

A `Submodel` represents a particular scope and holds the symbol-table
and constraints tree for that scope.

This commit refactors `Model` to be a wrapper over `Submodel`, and
removes methods operating on constraints and the symbol table from
`Model`, placing them in `Submodel` instead. A `Model` can be borrowed as
a `Submodel` using `as_submodel()` and `as_submodel_mut()`.

The language semantics of a top level model and a sub-model are
identical, so treating it as `Submodel` in most cases is valid.

`Model` is a separate type than `Submodel` for the following reasons:

  + It will hold global-only information in the future, such as dominance
    constraints.

  + It holds a pointer to the context.

  + We need special initialisation and de-serialisation logic for the
    top level model that we do not want for `Submodel`. See
    `SerdeModel`.
  • Loading branch information
niklasdewally committed Feb 17, 2025
1 parent 4043234 commit 542c89e
Show file tree
Hide file tree
Showing 22 changed files with 407 additions and 222 deletions.
15 changes: 3 additions & 12 deletions conjure_oxide/examples/solver-hello-minion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ use std::collections::HashMap;
#[allow(clippy::unwrap_used)]
pub fn main() {
use conjure_core::solver::SolverFamily;
use conjure_core::{
ast::pretty::pretty_expressions_as_top_level, parse::get_example_model,
rule_engine::resolve_rule_sets,
};
use conjure_core::{parse::get_example_model, rule_engine::resolve_rule_sets};
use conjure_core::{
rule_engine::rewrite_model,
solver::{adaptors, Solver},
Expand All @@ -25,19 +22,13 @@ pub fn main() {

// Load an example model and rewrite it with conjure oxide.
let model = get_example_model("div-05").unwrap();
println!(
"Input model: \n {} \n",
pretty_expressions_as_top_level(&model.get_constraints_vec())
);
println!("Input model: \n {model} \n",);

// TODO: We will have a nicer way to do this in the future
let rule_sets = resolve_rule_sets(SolverFamily::Minion, &get_default_rule_sets()).unwrap();

let model = rewrite_model(&model, &rule_sets).unwrap();
println!(
"Rewritten model: \n {} \n",
pretty_expressions_as_top_level(&model.get_constraints_vec())
);
println!("Rewritten model: \n {model} \n",);

// To tell the `Solver` type what solver to use, you pass it a `SolverAdaptor`.
// Here we use Minion.
Expand Down
9 changes: 5 additions & 4 deletions conjure_oxide/src/utils/essence_parser.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![allow(clippy::legacy_numeric_constants)]
use conjure_core::ast::declaration::Declaration;
use conjure_core::ast::Declaration;
use conjure_core::error::Error;
use std::fs;
use std::rc::Rc;
Expand All @@ -23,7 +23,7 @@ pub fn parse_essence_file_native(
) -> Result<Model, EssenceParseError> {
let (tree, source_code) = get_tree(path, filename, extension);

let mut model = Model::new_empty(context);
let mut model = Model::new(context);
let root_node = tree.root_node();
for statement in named_children(&root_node) {
match statement.kind() {
Expand All @@ -32,6 +32,7 @@ pub fn parse_essence_file_native(
let var_hashmap = parse_find_statement(statement, &source_code);
for (name, decision_variable) in var_hashmap {
model
.as_submodel_mut()
.symbols_mut()
.insert(Rc::new(Declaration::new_var(name, decision_variable)));
}
Expand All @@ -43,12 +44,12 @@ pub fn parse_essence_file_native(
constraint_vec.push(parse_constraint(constraint, &source_code));
}
}
model.add_constraints(constraint_vec);
model.as_submodel_mut().add_constraints(constraint_vec);
}
"e_prime_label" => {}
"letting_statement_list" => {
let letting_vars = parse_letting_statement(statement, &source_code);
model.symbols_mut().extend(letting_vars);
model.as_submodel_mut().symbols_mut().extend(letting_vars);
}
_ => {
let kind = statement.kind();
Expand Down
2 changes: 1 addition & 1 deletion conjure_oxide/src/utils/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::hash::Hash;
use std::io::Write;
use std::sync::{Arc, RwLock};

use conjure_core::ast::model::SerdeModel;
use conjure_core::ast::SerdeModel;
use conjure_core::context::Context;
use serde_json::{json, Error as JsonError, Value as JsonValue};

Expand Down
18 changes: 9 additions & 9 deletions conjure_oxide/tests/model_tests.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
// Tests for various functionalities of the Model

use std::{cell::RefCell, rc::Rc};
use std::rc::Rc;

use conjure_core::ast::Model;
use conjure_oxide::ast::*;
use declaration::Declaration;

#[test]
fn modify_domain() {
let mut m = Model::new(Default::default());

let mut symbols = m.as_submodel_mut().symbols_mut();

let a = Name::UserName(String::from("a"));

let d1 = Domain::IntDomain(vec![Range::Bounded(1, 3)]);
let d2 = Domain::IntDomain(vec![Range::Bounded(1, 2)]);

let mut symbols = SymbolTable::new();
symbols
.insert(Rc::new(Declaration::new_var(a.clone(), d1.clone())))
.unwrap();

let m = Model::new(Rc::new(RefCell::new(symbols)), vec![], Default::default());

assert_eq!(&m.symbols().domain(&a).unwrap(), &d1);
assert_eq!(symbols.domain(&a).unwrap(), d1);

let mut decl_a = m.symbols().lookup(&a).unwrap();
let mut decl_a = symbols.lookup(&a).unwrap();

Rc::make_mut(&mut decl_a).as_var_mut().unwrap().domain = d2.clone();

m.symbols_mut().update_insert(decl_a);
symbols.update_insert(decl_a);

assert_eq!(&m.symbols().domain(&a).unwrap(), &d2);
assert_eq!(symbols.domain(&a).unwrap(), d2);
}
38 changes: 13 additions & 25 deletions conjure_oxide/tests/rewrite_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::process::exit;
use std::rc::Rc;
Expand All @@ -13,7 +12,6 @@ use conjure_oxide::{
solver::{adaptors, Solver},
Metadata, Model, Rule,
};
use declaration::Declaration;
use uniplate::{Biplate, Uniplate};

fn var_name_from_atom(a: &Atom) -> Name {
Expand Down Expand Up @@ -325,26 +323,27 @@ fn reduce_solve_xyz() {
)
);

let model = Model::new(
Rc::new(RefCell::new(SymbolTable::new())),
vec![expr1, expr2],
Default::default(),
);
let mut model = Model::new(Default::default());
*model.as_submodel_mut().constraints_mut() = vec![expr1, expr2];

model
.as_submodel_mut()
.symbols_mut()
.insert(Rc::new(Declaration::new_var(
Name::UserName(String::from("a")),
Domain::IntDomain(vec![Range::Bounded(1, 3)]),
)))
.unwrap();
model
.as_submodel_mut()
.symbols_mut()
.insert(Rc::new(Declaration::new_var(
Name::UserName(String::from("b")),
Domain::IntDomain(vec![Range::Bounded(1, 3)]),
)))
.unwrap();
model
.as_submodel_mut()
.symbols_mut()
.insert(Rc::new(Declaration::new_var(
Name::UserName(String::from("c")),
Expand Down Expand Up @@ -667,45 +666,34 @@ fn rewrite_solve_xyz() {
};

// Apply rewrite function to the nested expression
let rewritten_expr = rewrite_naive(
&Model::new(
Rc::new(RefCell::new(SymbolTable::new())),
vec![nested_expr],
Default::default(),
),
&rule_sets,
true,
)
.unwrap()
.get_constraints_vec();
let mut model = Model::new(Default::default());
*model.as_submodel_mut().constraints_mut() = vec![nested_expr];
model = rewrite_naive(&model, &rule_sets, true).unwrap();
let rewritten_expr = model.as_submodel().constraints();

// Check if the expression is in its simplest form

assert!(rewritten_expr.iter().all(is_simple));

// Create model with variables and constraints
let model = Model::new(
Rc::new(RefCell::new(SymbolTable::new())),
rewritten_expr,
Default::default(),
);

// Insert variables and domains
model
.as_submodel_mut()
.symbols_mut()
.insert(Rc::new(Declaration::new_var(
var_name_from_atom(&variable_a.clone()),
domain.clone(),
)))
.unwrap();
model
.as_submodel_mut()
.symbols_mut()
.insert(Rc::new(Declaration::new_var(
var_name_from_atom(&variable_b.clone()),
domain.clone(),
)))
.unwrap();
model
.as_submodel_mut()
.symbols_mut()
.insert(Rc::new(Declaration::new_var(
var_name_from_atom(&variable_c.clone()),
Expand Down
5 changes: 3 additions & 2 deletions crates/conjure_core/src/ast/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,21 @@ use crate::ast::Name;
use crate::ast::ReturnType;
use crate::metadata::Metadata;

use super::{Domain, Range};
use super::{Domain, Range, SubModel};

/// Represents different types of expressions used to define rules and constraints in the model.
///
/// The `Expression` enum includes operations, constants, and variable references
/// used to build rules and conditions for the model.
#[document_compatibility]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Uniplate)]
#[uniplate(walk_into=[Atom])]
#[uniplate(walk_into=[Atom,Submodel])]
#[biplate(to=Literal)]
#[biplate(to=Metadata)]
#[biplate(to=Atom)]
#[biplate(to=Name)]
#[biplate(to=Vec<Expression>)]
#[biplate(to=SubModel)]
pub enum Expression {
/// The top of the model
Root(Metadata, Vec<Expression>),
Expand Down
15 changes: 9 additions & 6 deletions crates/conjure_core/src/ast/mod.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
pub mod pretty;
pub mod types;
pub mod serde;

mod atom;
pub mod declaration;
mod declaration;
mod domains;
mod expressions;
mod literals;
pub mod model;
mod model;
mod name;
pub mod serde;
mod submodel;
mod symbol_table;
mod types;
mod variables;

pub use atom::Atom;
pub use declaration::*;
pub use domains::Domain;
pub use domains::Range;
pub use expressions::Expression;
pub use literals::Literal;
pub use model::Model;
pub use model::*;
pub use name::Name;
pub use submodel::SubModel;
pub use symbol_table::SymbolTable;
pub use types::ReturnType;
pub use types::*;
pub use variables::DecisionVariable;
Loading

0 comments on commit 542c89e

Please sign in to comment.