Skip to content

Optimization

The Optimization client uses the backward chaining engine’s embedded CLP(Q) simplex solver to solve linear programs. Unlike standalone solvers, optimization here is tightly integrated with the knowledge base — you can formulate problems directly from existing sorts and terms.

The LP namespace provides builder functions for constructing linear program definitions.

import { LP } from '@kortexya/reasoninglayer';
// Objective function
const objective = LP.maximize({ chairs: 3, tables: 5 });
// or
const minObjective = LP.minimize({ cost: 2, waste: 1 });
// Constraints
const woodConstraint = LP.constraint({ chairs: 1, tables: 3 }, '<=', 12);
const laborConstraint = LP.constraint({ chairs: 2, tables: 1 }, '<=', 8, 'labor');
// Variable bounds
const bounds = LP.nonNegative('chairs', 'tables');
// { chairs: { min: 0 }, tables: { min: 0 } }
// Custom bounds
const customBounds = {
x: LP.bounds(0, 100), // 0 <= x <= 100
y: LP.bounds(0), // y >= 0
z: LP.bounds(undefined, 50), // z <= 50
};
Method Description Returns
LP.maximize(coefficients) Maximize a linear expression ObjectiveFunction
LP.minimize(coefficients) Minimize a linear expression ObjectiveFunction
LP.constraint(coefficients, op, rhs, label?) Create a linear constraint LinearConstraint
LP.nonNegative(...vars) Create non-negativity bounds for variables Record<string, VariableBounds>
LP.bounds(min?, max?) Create custom variable bounds VariableBounds
Operator Meaning
'<=' Less than or equal
'>=' Greater than or equal
'=' Equality

Use client.optimize.solve() with a LinearProgramDefinition:

import { LP } from '@kortexya/reasoninglayer';
const result = await client.optimize.solve({
objective: LP.maximize({ chairs: 3, tables: 5 }),
constraints: [
LP.constraint({ chairs: 1, tables: 3 }, '<=', 12, 'wood'),
LP.constraint({ chairs: 2, tables: 1 }, '<=', 8, 'labor'),
],
bounds: LP.nonNegative('chairs', 'tables'),
});
if (result.status === 'optimal') {
console.log(result.variables); // { chairs: 2.4, tables: 3.2 }
console.log(result.objectiveValue); // 23.2
console.log(result.solveTimeMs); // solve time in milliseconds
} else if (result.status === 'infeasible') {
// The engine's derivation was complete and found no solution.
console.log('No feasible solution exists');
} else {
// result.status === 'undetermined': the search was bounded, feasibility is unknown.
console.log('No solution found within the bounds; retry with a larger timeoutMs or maxDepth');
}

Pass a second argument to configure the solver:

const result = await client.optimize.solve(problem, {
timeoutMs: 60000, // solver timeout (default: 30000)
maxDepth: 100, // backward chaining depth limit (default: 50)
cleanup: false, // keep temporary sort and rule (default: true)
});

The real power is formulating LPs from existing knowledge base data. Instead of specifying coefficients manually, fromKnowledgeBase() queries your sorts and terms to build the problem automatically.

Given products stored as terms with features like profit, wood_cost, and labor_cost:

// Products already exist in the KB as terms of sort "product"
// Each product has features: name, profit, wood_cost, labor_cost
const result = await client.optimize.fromKnowledgeBase({
variables: {
sort: 'product', // which sort to query
nameFeature: 'name', // feature used as variable name
},
objective: {
direction: 'maximize',
coefficientFeature: 'profit', // feature used as objective coefficient
},
resourceConstraints: [
{ costFeature: 'wood_cost', capacity: 12, label: 'wood' },
{ costFeature: 'labor_cost', capacity: 8, label: 'labor' },
],
nonNegative: true,
});
if (result.result.status === 'optimal') {
console.log(result.result.variables); // { chair: 2.4, table: 3.2 }
console.log(result.result.objectiveValue);
}
// Inspect the auto-generated LP
console.log(result.generatedProblem);
// See the terms that were used
console.log(result.discoveredTerms);
  1. Resolves the sort — finds the sort by name in the tenant’s sort list
  2. Queries all terms — retrieves all terms of that sort
  3. Extracts coefficients — reads the named features from each term to build the objective and constraint coefficient maps
  4. Builds the LP — constructs a LinearProgramDefinition from the extracted data
  5. Solves — passes the problem to solve() and returns the result plus the generated problem for inspection

You can add extra constraints beyond what’s derived from the KB:

const result = await client.optimize.fromKnowledgeBase({
variables: { sort: 'product', nameFeature: 'name' },
objective: { direction: 'maximize', coefficientFeature: 'profit' },
resourceConstraints: [{ costFeature: 'wood_cost', capacity: 12 }],
additionalConstraints: [LP.constraint({ chair: 1, table: 1 }, '>=', 2, 'minimum_production')],
nonNegative: true,
});

Resource constraints default to <= but you can specify the operator:

resourceConstraints: [
{ costFeature: 'wood_cost', capacity: 12, op: '<=' }, // default
{ costFeature: 'min_demand', capacity: 5, op: '>=' }, // minimum demand
],

The OptimizationResult is a discriminated union:

type OptimizationResult =
| {
status: 'optimal';
variables: Record<string, number>;
objectiveValue: number;
solveTimeMs: number;
}
| { status: 'infeasible'; solveTimeMs: number }
| { status: 'undetermined'; solveTimeMs: number };

Always check result.status before accessing variables or objectiveValue.

infeasible is reported only when the engine’s backward-chaining derivation was complete and found no solution. When the derivation was bounded (a depth, time or solution limit, or a pending constraint) and found nothing, the result is undetermined: the problem may still be feasible.

The optimization client compiles LP problems into the engine’s native constraint sort vocabulary:

  1. Variables become logic variables (?chairs, ?tables)
  2. Coefficients become real_times_constraint antecedents (optimized away when coefficient is 1)
  3. Linear expressions are chained with real_plus_constraint nodes
  4. Inequality constraints become real_le_constraint, real_ge_constraint, or real_eq_constraint
  5. Bounds become real_ge_constraint, real_le_constraint, or real_between_constraint
  6. The objective becomes a maximize_objective or minimize_objective
  7. A real_labeling_constraint triggers the CLP(Q) simplex solver

The compiled rule is submitted via addRule, then backwardChain triggers the solver. Solutions come back as variable bindings with exact rational arithmetic (Rational64), converted to JavaScript numbers.

Users never need to interact with the constraint sort vocabulary directly — the LP builder and OptimizeClient handle everything.