forked from AuroraMiddleware/gtk
2242 lines
69 KiB
C
2242 lines
69 KiB
C
/* gtkconstraintsolver.c: Constraint solver based on the Cassowary method
|
||
* Copyright 2019 GNOME Foundation
|
||
*
|
||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||
*
|
||
* This library is free software; you can redistribute it and/or
|
||
* modify it under the terms of the GNU Lesser General Public
|
||
* License as published by the Free Software Foundation; either
|
||
* version 2.1 of the License, or (at your option) any later version.
|
||
*
|
||
* This library is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
* Lesser General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU Lesser General Public
|
||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||
*
|
||
* Author: Emmanuele Bassi
|
||
*/
|
||
|
||
/*< private >
|
||
* GtkConstraintSolver
|
||
*
|
||
* GtkConstraintSolver is an object that encodes constraints into a tableau
|
||
* of linear equations and solves them, using an incremental optimization
|
||
* algorithm known as the "Cassowary Linear Arithmetic Constraint Solving
|
||
* Algorithm" (Badros, Borning & Stuckey 2001).
|
||
*
|
||
* Each constraint is expressed as a linear equation, whose terms are variables
|
||
* containing widget attributes like the width, height, or position; the simplex
|
||
* solver takes all the constraints and incrementally optimizes the tableau to
|
||
* replace known terms; additionally, the algorithm will try to assign a value
|
||
* to all remaining variables in order to satisfy the various constraints.
|
||
*
|
||
* Each constraint is given a "strength", which determines whether satisfying
|
||
* the constraint is required in order to solve the tableau or not.
|
||
*
|
||
* A typical example of GtkConstraintSolver use is solving the following
|
||
* system of constraints:
|
||
*
|
||
* - [required] right = left + 10
|
||
* - [required] right ≤ 100
|
||
* - [required] middle = left + right / 2
|
||
* - [required] left ≥ 0
|
||
*
|
||
* Once we create a GtkConstraintSolver instance, we need to create the
|
||
* various variables and expressions that represent the values we want to
|
||
* compute and the constraints we wish to impose on the solutions:
|
||
*
|
||
* |[
|
||
* GtkConstraintSolver *solver = gtk_constraint_solver_new ();
|
||
*
|
||
* // Our variables
|
||
* GtkConstraintVariable *left =
|
||
* gtk_constraint_solver_create_variable (solver, NULL, "left", 0.0);
|
||
* GtkConstraintVariable *middle =
|
||
* gtk_constraint_solver_create_variable (solver, NULL, "middle", 0.0);
|
||
* GtkConstraintVariable *right =
|
||
* gtk_constraint_solver_create_variable (solver, NULL, "right", 0.0);
|
||
*
|
||
* // Our constraints
|
||
* GtkConstraintExpressionBuilder builder;
|
||
* GtkConstraintExpression *e;
|
||
*
|
||
* // right = left + 10
|
||
* gtk_constraint_expression_builder_init (&builder, solver);
|
||
* gtk_constraint_expression_builder_term (&builder, left);
|
||
* gtk_constraint_expression_builder_plus (&builder);
|
||
* gtk_constraint_expression_builder_constant (&builder, 10.0);
|
||
* e = gtk_constraint_expression_builder_finish (&builder);
|
||
* gtk_constraint_solver_add_constraint (solver,
|
||
* right, GTK_CONSTRAINT_RELATION_EQ, e,
|
||
* GTK_CONSTRAINT_STRENGTH_REQUIRED);
|
||
*
|
||
* // right ≤ 100
|
||
* gtk_constraint_expression_builder_constant (&builder, 100.0);
|
||
* e = gtk_constraint_expression_builder_finish (&builder);
|
||
* gtk_constraint_solver_add_constraint (solver,
|
||
* right, GTK_CONSTRAINT_RELATION_LE, e,
|
||
* GTK_CONSTRAINT_STRENGTH_REQUIRED);
|
||
*
|
||
* // middle = (left + right) / 2
|
||
* gtk_constraint_expression_builder_term (&builder, left);
|
||
* gtk_constraint_expression_builder_plus (&builder)
|
||
* gtk_constraint_expression_builder_term (&builder, right);
|
||
* gtk_constraint_expression_builder_divide_by (&builder);
|
||
* gtk_constraint_expression_builder_constant (&builder, 2.0);
|
||
* e = gtk_constraint_expression_builder_finish (&builder);
|
||
* gtk_constraint_solver_add_constraint (solver
|
||
* middle, GTK_CONSTRAINT_RELATION_EQ, e,
|
||
* GTK_CONSTRAINT_STRENGTH_REQUIRED);
|
||
*
|
||
* // left ≥ 0
|
||
* gtk_constraint_expression_builder_constant (&builder, 0.0);
|
||
* e = gtk_constraint_expression_builder_finish (&builder);
|
||
* gtk_constraint_solver_add_constraint (solver,
|
||
* left, GTK_CONSTRAINT_RELATION_GE, e,
|
||
* GTK_CONSTRAINT_STRENGTH_REQUIRED);
|
||
* ]|
|
||
*
|
||
* Now that we have all our constraints in place, suppose we wish to find
|
||
* the values of `left` and `right` if we specify the value of `middle`. In
|
||
* order to do that, we need to add an additional "stay" constraint, i.e.
|
||
* a constraint that tells the solver to optimize for the solution that keeps
|
||
* the variable in place:
|
||
*
|
||
* |[
|
||
* // Set the value first
|
||
* gtk_constraint_variable_set_value (middle, 45.0);
|
||
* // and then add the stay constraint, with a weak strength
|
||
* gtk_constraint_solver_add_stay_variable (solver, middle, GTK_CONSTRAINT_STRENGTH_WEAK);
|
||
* ]|
|
||
*
|
||
* GtkConstraintSolver incrementally solves the system every time a constraint
|
||
* is added or removed, so it's possible to query the values of the variables
|
||
* immediately afterward:
|
||
*
|
||
* |[
|
||
* double left_val = gtk_constraint_variable_get_value (left);
|
||
* double right_val = gtk_constraint_variable_get_value (right);
|
||
* double middle_val = gtk_constraint_variable_get_value (middle);
|
||
*
|
||
* // These are the values computed by the solver:
|
||
* g_assert_cmpfloat_with_epsilon (left_val, 40.0, 0.001);
|
||
* g_assert_cmpfloat_with_epsilon (middle_val, 45.0, 0.001);
|
||
* g_assert_cmpfloat_with_epsilon (right_val, 50.0, 0.001);
|
||
* ]|
|
||
*
|
||
* As you can see:
|
||
*
|
||
* - the middle value hasn't changed
|
||
* - the left value is ≥ 0
|
||
* - the right value is ≤ 100
|
||
* - the right value is left + 10
|
||
* - the middle value is (left + right) / 2.0
|
||
*
|
||
* For more information about the Cassowary constraint solving algorithm and
|
||
* toolkit, see the following papers:
|
||
*
|
||
* - Badros G & Borning A, 1998, 'Cassowary Linear Arithmetic Constraint
|
||
* Solving Algorithm: Interface and Implementation', Technical Report
|
||
* UW-CSE-98-06-04, June 1998 (revised July 1999)
|
||
* https://constraints.cs.washington.edu/cassowary/cassowary-tr.pdf
|
||
* - Badros G, Borning A & Stuckey P, 2001, 'Cassowary Linear Arithmetic
|
||
* Constraint Solving Algorithm', ACM Transactions on Computer-Human
|
||
* Interaction, vol. 8 no. 4, December 2001, pages 267-306
|
||
* https://constraints.cs.washington.edu/solvers/cassowary-tochi.pdf
|
||
*
|
||
* The following implementation is based on these projects:
|
||
*
|
||
* - the original [C++ implementation](https://sourceforge.net/projects/cassowary/)
|
||
* - the JavaScript port [Cassowary.js](https://github.com/slightlyoff/cassowary.js)
|
||
* - the Python port [Cassowary](https://github.com/pybee/cassowary)
|
||
*/
|
||
|
||
#include "config.h"
|
||
|
||
#include "gtkconstraintsolverprivate.h"
|
||
#include "gtkconstraintexpressionprivate.h"
|
||
|
||
#include "gtkdebug.h"
|
||
#include "gtkprivate.h"
|
||
|
||
#include <glib.h>
|
||
#include <string.h>
|
||
#include <math.h>
|
||
#include <float.h>
|
||
|
||
struct _GtkConstraintRef
|
||
{
|
||
/* The constraint's normal form inside the solver:
|
||
*
|
||
* x - (y × coefficient + constant) = 0
|
||
*
|
||
* We only use equalities, and replace inequalities with slack
|
||
* variables.
|
||
*/
|
||
GtkConstraintExpression *expression;
|
||
|
||
/* A constraint variable, only used by stay and edit constraints */
|
||
GtkConstraintVariable *variable;
|
||
|
||
/* The original relation used when creating the constraint */
|
||
GtkConstraintRelation relation;
|
||
|
||
/* The strength of the constraint; this value is used to strengthen
|
||
* or weaken a constraint weight in the tableau when coming to a
|
||
* solution
|
||
*/
|
||
int strength;
|
||
|
||
GtkConstraintSolver *solver;
|
||
|
||
guint is_edit : 1;
|
||
guint is_stay : 1;
|
||
};
|
||
|
||
typedef struct {
|
||
GtkConstraintRef *constraint;
|
||
|
||
GtkConstraintVariable *eplus;
|
||
GtkConstraintVariable *eminus;
|
||
|
||
double prev_constant;
|
||
} EditInfo;
|
||
|
||
typedef struct {
|
||
GtkConstraintRef *constraint;
|
||
} StayInfo;
|
||
|
||
struct _GtkConstraintSolver
|
||
{
|
||
GObject parent_instance;
|
||
|
||
/* HashTable<Variable, VariableSet>; owns keys and values */
|
||
GHashTable *columns;
|
||
/* HashTable<Variable, Expression>; owns keys and values */
|
||
GHashTable *rows;
|
||
|
||
/* Set<Variable>; does not own keys */
|
||
GHashTable *external_rows;
|
||
/* Set<Variable>; does not own keys */
|
||
GHashTable *external_parametric_vars;
|
||
|
||
/* Vec<Variable> */
|
||
GPtrArray *infeasible_rows;
|
||
/* Vec<VariablePair>; owns the pair */
|
||
GPtrArray *stay_error_vars;
|
||
|
||
/* HashTable<Constraint, VariableSet>; owns the set */
|
||
GHashTable *error_vars;
|
||
/* HashTable<Constraint, Variable> */
|
||
GHashTable *marker_vars;
|
||
|
||
/* HashTable<Variable, EditInfo>; does not own keys, but owns values */
|
||
GHashTable *edit_var_map;
|
||
/* HashTable<Variable, StayInfo>; does not own keys, but owns values */
|
||
GHashTable *stay_var_map;
|
||
|
||
GtkConstraintVariable *objective;
|
||
|
||
/* Set<Constraint>; owns the key */
|
||
GHashTable *constraints;
|
||
|
||
/* Counters */
|
||
int var_counter;
|
||
int slack_counter;
|
||
int artificial_counter;
|
||
int dummy_counter;
|
||
int optimize_count;
|
||
int freeze_count;
|
||
|
||
/* Bitfields; keep at the end */
|
||
guint auto_solve : 1;
|
||
guint needs_solving : 1;
|
||
guint in_edit_phase : 1;
|
||
};
|
||
|
||
static void gtk_constraint_ref_free (GtkConstraintRef *ref);
|
||
static void edit_info_free (gpointer data);
|
||
|
||
G_DEFINE_TYPE (GtkConstraintSolver, gtk_constraint_solver, G_TYPE_OBJECT)
|
||
|
||
static void
|
||
gtk_constraint_solver_finalize (GObject *gobject)
|
||
{
|
||
GtkConstraintSolver *self = GTK_CONSTRAINT_SOLVER (gobject);
|
||
|
||
g_hash_table_remove_all (self->constraints);
|
||
g_clear_pointer (&self->constraints, g_hash_table_unref);
|
||
|
||
g_clear_pointer (&self->stay_error_vars, g_ptr_array_unref);
|
||
g_clear_pointer (&self->infeasible_rows, g_ptr_array_unref);
|
||
|
||
g_clear_pointer (&self->external_rows, g_hash_table_unref);
|
||
g_clear_pointer (&self->external_parametric_vars, g_hash_table_unref);
|
||
g_clear_pointer (&self->error_vars, g_hash_table_unref);
|
||
g_clear_pointer (&self->marker_vars, g_hash_table_unref);
|
||
g_clear_pointer (&self->edit_var_map, g_hash_table_unref);
|
||
g_clear_pointer (&self->stay_var_map, g_hash_table_unref);
|
||
|
||
g_clear_pointer (&self->rows, g_hash_table_unref);
|
||
g_clear_pointer (&self->columns, g_hash_table_unref);
|
||
|
||
G_OBJECT_CLASS (gtk_constraint_solver_parent_class)->finalize (gobject);
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_class_init (GtkConstraintSolverClass *klass)
|
||
{
|
||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||
|
||
gobject_class->finalize = gtk_constraint_solver_finalize;
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_init (GtkConstraintSolver *self)
|
||
{
|
||
self->columns =
|
||
g_hash_table_new_full (NULL, NULL,
|
||
(GDestroyNotify) gtk_constraint_variable_unref,
|
||
(GDestroyNotify) gtk_constraint_variable_set_free);
|
||
|
||
self->rows =
|
||
g_hash_table_new_full (NULL, NULL,
|
||
(GDestroyNotify) gtk_constraint_variable_unref,
|
||
(GDestroyNotify) gtk_constraint_expression_unref);
|
||
|
||
self->external_rows = g_hash_table_new (NULL, NULL);
|
||
|
||
self->external_parametric_vars = g_hash_table_new (NULL, NULL);
|
||
|
||
self->infeasible_rows = g_ptr_array_new ();
|
||
|
||
self->stay_error_vars =
|
||
g_ptr_array_new_with_free_func ((GDestroyNotify) gtk_constraint_variable_pair_free);
|
||
|
||
self->error_vars =
|
||
g_hash_table_new_full (NULL, NULL,
|
||
NULL,
|
||
(GDestroyNotify) gtk_constraint_variable_set_free);
|
||
|
||
self->marker_vars = g_hash_table_new (NULL, NULL);
|
||
|
||
self->edit_var_map = g_hash_table_new_full (NULL, NULL,
|
||
NULL,
|
||
edit_info_free);
|
||
|
||
self->stay_var_map = g_hash_table_new_full (NULL, NULL,
|
||
NULL,
|
||
g_free);
|
||
|
||
/* The rows table owns the objective variable */
|
||
self->objective = gtk_constraint_variable_new_objective ("Z");
|
||
g_hash_table_insert (self->rows,
|
||
self->objective,
|
||
gtk_constraint_expression_new (0.0));
|
||
|
||
self->constraints =
|
||
g_hash_table_new_full (NULL, NULL,
|
||
(GDestroyNotify) gtk_constraint_ref_free,
|
||
NULL);
|
||
|
||
self->slack_counter = 0;
|
||
self->dummy_counter = 0;
|
||
self->artificial_counter = 0;
|
||
self->freeze_count = 0;
|
||
|
||
self->needs_solving = FALSE;
|
||
self->auto_solve = TRUE;
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_ref_free (GtkConstraintRef *self)
|
||
{
|
||
gtk_constraint_solver_remove_constraint (self->solver, self);
|
||
|
||
gtk_constraint_expression_unref (self->expression);
|
||
|
||
if (self->is_edit || self->is_stay)
|
||
{
|
||
g_assert (self->variable != NULL);
|
||
gtk_constraint_variable_unref (self->variable);
|
||
}
|
||
|
||
g_free (self);
|
||
}
|
||
|
||
static gboolean
|
||
gtk_constraint_ref_is_inequality (const GtkConstraintRef *self)
|
||
{
|
||
return self->relation != GTK_CONSTRAINT_RELATION_EQ;
|
||
}
|
||
|
||
static gboolean
|
||
gtk_constraint_ref_is_required (const GtkConstraintRef *self)
|
||
{
|
||
return self->strength == GTK_CONSTRAINT_STRENGTH_REQUIRED;
|
||
}
|
||
|
||
static const char *relations[] = {
|
||
"<=",
|
||
"==",
|
||
">=",
|
||
};
|
||
|
||
static const char *
|
||
relation_to_string (GtkConstraintRelation r)
|
||
{
|
||
return relations[r + 1];
|
||
}
|
||
|
||
static const char *
|
||
strength_to_string (int s)
|
||
{
|
||
if (s >= GTK_CONSTRAINT_STRENGTH_STRONG)
|
||
return "strong";
|
||
|
||
if (s >= GTK_CONSTRAINT_STRENGTH_MEDIUM)
|
||
return "medium";
|
||
|
||
return "weak";
|
||
}
|
||
|
||
static char *
|
||
gtk_constraint_ref_to_string (const GtkConstraintRef *self)
|
||
{
|
||
GString *buf = g_string_new (NULL);
|
||
char *str;
|
||
|
||
if (self->is_stay)
|
||
g_string_append (buf, "[stay]");
|
||
else if (self->is_edit)
|
||
g_string_append (buf, "[edit]");
|
||
|
||
str = gtk_constraint_expression_to_string (self->expression);
|
||
g_string_append (buf, str);
|
||
g_free (str);
|
||
|
||
g_string_append_c (buf, ' ');
|
||
g_string_append (buf, relation_to_string (self->relation));
|
||
g_string_append (buf, " 0.0");
|
||
|
||
if (gtk_constraint_ref_is_required (self))
|
||
g_string_append (buf, " [strength:required]");
|
||
else
|
||
g_string_append_printf (buf, " [strength:%d (%s)]",
|
||
self->strength,
|
||
strength_to_string (self->strength));
|
||
|
||
return g_string_free (buf, FALSE);
|
||
}
|
||
|
||
static GtkConstraintVariableSet *
|
||
gtk_constraint_solver_get_column_set (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *param_var)
|
||
{
|
||
return g_hash_table_lookup (self->columns, param_var);
|
||
}
|
||
|
||
static gboolean
|
||
gtk_constraint_solver_column_has_key (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *subject)
|
||
{
|
||
return g_hash_table_contains (self->columns, subject);
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_insert_column_variable (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *param_var,
|
||
GtkConstraintVariable *row_var)
|
||
{
|
||
GtkConstraintVariableSet *cset = g_hash_table_lookup (self->columns, param_var);
|
||
|
||
if (cset == NULL)
|
||
{
|
||
cset = gtk_constraint_variable_set_new ();
|
||
g_hash_table_insert (self->columns, gtk_constraint_variable_ref (param_var), cset);
|
||
}
|
||
|
||
if (row_var != NULL)
|
||
gtk_constraint_variable_set_add (cset, row_var);
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_insert_error_variable (GtkConstraintSolver *self,
|
||
GtkConstraintRef *constraint,
|
||
GtkConstraintVariable *variable)
|
||
{
|
||
GtkConstraintVariableSet *cset = g_hash_table_lookup (self->error_vars, constraint);
|
||
|
||
if (cset == NULL)
|
||
{
|
||
cset = gtk_constraint_variable_set_new ();
|
||
g_hash_table_insert (self->error_vars, constraint, cset);
|
||
}
|
||
|
||
gtk_constraint_variable_set_add (cset, variable);
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_reset_stay_constants (GtkConstraintSolver *self)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < self->stay_error_vars->len; i++)
|
||
{
|
||
GtkConstraintVariablePair *pair = g_ptr_array_index (self->stay_error_vars, i);
|
||
GtkConstraintExpression *expression;
|
||
|
||
expression = g_hash_table_lookup (self->rows, pair->first);
|
||
|
||
if (expression == NULL)
|
||
expression = g_hash_table_lookup (self->rows, pair->second);
|
||
|
||
if (expression != NULL)
|
||
gtk_constraint_expression_set_constant (expression, 0.0);
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_set_external_variables (GtkConstraintSolver *self)
|
||
{
|
||
GHashTableIter iter;
|
||
gpointer key_p;
|
||
|
||
g_hash_table_iter_init (&iter, self->external_parametric_vars);
|
||
while (g_hash_table_iter_next (&iter, &key_p, NULL))
|
||
{
|
||
GtkConstraintVariable *variable = key_p;
|
||
|
||
if (g_hash_table_contains (self->rows, variable))
|
||
continue;
|
||
|
||
gtk_constraint_variable_set_value (variable, 0.0);
|
||
}
|
||
|
||
g_hash_table_iter_init (&iter, self->external_rows);
|
||
while (g_hash_table_iter_next (&iter, &key_p, NULL))
|
||
{
|
||
GtkConstraintVariable *variable = key_p;
|
||
GtkConstraintExpression *expression;
|
||
double constant;
|
||
|
||
expression = g_hash_table_lookup (self->rows, variable);
|
||
constant = gtk_constraint_expression_get_constant (expression);
|
||
|
||
gtk_constraint_variable_set_value (variable, constant);
|
||
}
|
||
|
||
self->needs_solving = FALSE;
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_add_row (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *variable,
|
||
GtkConstraintExpression *expression)
|
||
{
|
||
GtkConstraintExpressionIter iter;
|
||
GtkConstraintVariable *t_v;
|
||
double t_c;
|
||
|
||
g_hash_table_insert (self->rows,
|
||
gtk_constraint_variable_ref (variable),
|
||
gtk_constraint_expression_ref (expression));
|
||
|
||
gtk_constraint_expression_iter_init (&iter, expression);
|
||
while (gtk_constraint_expression_iter_next (&iter, &t_v, &t_c))
|
||
{
|
||
gtk_constraint_solver_insert_column_variable (self, t_v, variable);
|
||
|
||
if (gtk_constraint_variable_is_external (t_v))
|
||
g_hash_table_add (self->external_parametric_vars, t_v);
|
||
}
|
||
|
||
if (gtk_constraint_variable_is_external (variable))
|
||
g_hash_table_add (self->external_rows, variable);
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_remove_column (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *variable)
|
||
{
|
||
GtkConstraintVariable *v;
|
||
GtkConstraintVariableSetIter iter;
|
||
GtkConstraintVariableSet *cset;
|
||
|
||
/* Take a reference on the variable, as we're going to remove it
|
||
* from various maps and we want to guarantee the pointer is
|
||
* valid until we leave this function
|
||
*/
|
||
gtk_constraint_variable_ref (variable);
|
||
|
||
cset = g_hash_table_lookup (self->columns, variable);
|
||
if (cset == NULL)
|
||
goto out;
|
||
|
||
gtk_constraint_variable_set_iter_init (&iter, cset);
|
||
while (gtk_constraint_variable_set_iter_next (&iter, &v))
|
||
{
|
||
GtkConstraintExpression *e = g_hash_table_lookup (self->rows, v);
|
||
|
||
gtk_constraint_expression_remove_variable (e, variable);
|
||
}
|
||
|
||
g_hash_table_remove (self->columns, variable);
|
||
|
||
out:
|
||
if (gtk_constraint_variable_is_external (variable))
|
||
{
|
||
g_hash_table_remove (self->external_rows, variable);
|
||
g_hash_table_remove (self->external_parametric_vars, variable);
|
||
}
|
||
|
||
gtk_constraint_variable_unref (variable);
|
||
}
|
||
|
||
static GtkConstraintExpression *
|
||
gtk_constraint_solver_remove_row (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *variable,
|
||
gboolean free_res)
|
||
{
|
||
GtkConstraintExpression *e;
|
||
GtkConstraintExpressionIter iter;
|
||
GtkConstraintVariable *t_v;
|
||
double t_c;
|
||
|
||
e = g_hash_table_lookup (self->rows, variable);
|
||
g_assert (e != NULL);
|
||
|
||
gtk_constraint_expression_ref (e);
|
||
|
||
gtk_constraint_expression_iter_init (&iter, e);
|
||
while (gtk_constraint_expression_iter_next (&iter, &t_v, &t_c))
|
||
{
|
||
GtkConstraintVariableSet *cset = g_hash_table_lookup (self->columns, t_v);
|
||
|
||
if (cset != NULL)
|
||
gtk_constraint_variable_set_remove (cset, variable);
|
||
}
|
||
|
||
g_ptr_array_remove (self->infeasible_rows, variable);
|
||
|
||
if (gtk_constraint_variable_is_external (variable))
|
||
g_hash_table_remove (self->external_rows, variable);
|
||
|
||
g_hash_table_remove (self->rows, variable);
|
||
|
||
if (free_res)
|
||
{
|
||
gtk_constraint_expression_unref (e);
|
||
return NULL;
|
||
}
|
||
|
||
return e;
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_substitute_out:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @old_variable: a `GtkConstraintVariable`
|
||
* @expression: a `GtkConstraintExpression`
|
||
*
|
||
* Replaces @old_variable in every row of the tableau with @expression.
|
||
*/
|
||
static void
|
||
gtk_constraint_solver_substitute_out (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *old_variable,
|
||
GtkConstraintExpression *expression)
|
||
{
|
||
GtkConstraintVariableSet *cset = g_hash_table_lookup (self->columns, old_variable);
|
||
if (cset != NULL)
|
||
{
|
||
GtkConstraintVariableSetIter iter;
|
||
GtkConstraintVariable *v;
|
||
|
||
gtk_constraint_variable_set_iter_init (&iter, cset);
|
||
while (gtk_constraint_variable_set_iter_next (&iter, &v))
|
||
{
|
||
GtkConstraintExpression *row = g_hash_table_lookup (self->rows, v);
|
||
|
||
gtk_constraint_expression_substitute_out (row, old_variable, expression, v, self);
|
||
|
||
if (gtk_constraint_variable_is_restricted (v) &&
|
||
gtk_constraint_expression_get_constant (row) < 0)
|
||
g_ptr_array_add (self->infeasible_rows, v);
|
||
}
|
||
}
|
||
|
||
if (gtk_constraint_variable_is_external (old_variable))
|
||
{
|
||
g_hash_table_add (self->external_rows, old_variable);
|
||
g_hash_table_remove (self->external_parametric_vars, old_variable);
|
||
}
|
||
|
||
g_hash_table_remove (self->columns, old_variable);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_pivot:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @entry_var: a `GtkConstraintVariable`
|
||
* @exit_var: a `GtkConstraintVariable`
|
||
*
|
||
* Pivots the `GtkConstraintSolver`.
|
||
*
|
||
* This function will move @entry_var into the basis of the tableau,
|
||
* making it a basic variable; and move @exit_var out of the basis of
|
||
* the tableau, making it a parametric variable.
|
||
*/
|
||
static void
|
||
gtk_constraint_solver_pivot (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *entry_var,
|
||
GtkConstraintVariable *exit_var)
|
||
{
|
||
GtkConstraintExpression *expr;
|
||
|
||
if (entry_var != NULL)
|
||
gtk_constraint_variable_ref (entry_var);
|
||
else
|
||
g_critical ("INTERNAL: invalid entry variable during pivot");
|
||
|
||
if (exit_var != NULL)
|
||
gtk_constraint_variable_ref (exit_var);
|
||
else
|
||
g_critical ("INTERNAL: invalid exit variable during pivot");
|
||
|
||
/* We keep a reference to the expression */
|
||
expr = gtk_constraint_solver_remove_row (self, exit_var, FALSE);
|
||
|
||
gtk_constraint_expression_change_subject (expr, exit_var, entry_var);
|
||
gtk_constraint_solver_substitute_out (self, entry_var, expr);
|
||
|
||
if (gtk_constraint_variable_is_external (entry_var))
|
||
g_hash_table_remove (self->external_parametric_vars, entry_var);
|
||
|
||
gtk_constraint_solver_add_row (self, entry_var, expr);
|
||
|
||
gtk_constraint_variable_unref (entry_var);
|
||
gtk_constraint_variable_unref (exit_var);
|
||
gtk_constraint_expression_unref (expr);
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_optimize (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *z)
|
||
{
|
||
GtkConstraintVariable *entry = NULL, *exit = NULL;
|
||
GtkConstraintExpression *z_row = g_hash_table_lookup (self->rows, z);
|
||
|
||
#ifdef G_ENABLE_DEBUG
|
||
gint64 start_time = g_get_monotonic_time ();
|
||
#endif
|
||
|
||
g_assert (z_row != NULL);
|
||
|
||
self->optimize_count += 1;
|
||
|
||
#ifdef G_ENABLE_DEBUG
|
||
if (GTK_DEBUG_CHECK (CONSTRAINTS))
|
||
{
|
||
char *str = gtk_constraint_variable_to_string (z);
|
||
g_message ("optimize: %s", str);
|
||
g_free (str);
|
||
}
|
||
#endif
|
||
|
||
while (TRUE)
|
||
{
|
||
GtkConstraintVariableSet *column_vars;
|
||
GtkConstraintVariableSetIter viter;
|
||
GtkConstraintExpressionIter eiter;
|
||
GtkConstraintVariable *t_v, *v;
|
||
double t_c;
|
||
double objective_coefficient = 0.0;
|
||
double min_ratio;
|
||
|
||
gtk_constraint_expression_iter_init (&eiter, z_row);
|
||
while (gtk_constraint_expression_iter_prev (&eiter, &t_v, &t_c))
|
||
{
|
||
if (gtk_constraint_variable_is_pivotable (t_v) && t_c < objective_coefficient)
|
||
{
|
||
entry = t_v;
|
||
objective_coefficient = t_c;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (objective_coefficient >= -1e-8)
|
||
break;
|
||
|
||
min_ratio = DBL_MAX;
|
||
|
||
column_vars = gtk_constraint_solver_get_column_set (self, entry);
|
||
gtk_constraint_variable_set_iter_init (&viter, column_vars);
|
||
while (gtk_constraint_variable_set_iter_next (&viter, &v))
|
||
{
|
||
if (gtk_constraint_variable_is_pivotable (v))
|
||
{
|
||
GtkConstraintExpression *expr = g_hash_table_lookup (self->rows, v);
|
||
double coeff = gtk_constraint_expression_get_coefficient (expr, entry);
|
||
|
||
if (coeff < 0.0)
|
||
{
|
||
double constant = gtk_constraint_expression_get_constant (expr);
|
||
|
||
double r = -1.0 * constant / coeff;
|
||
if (r < min_ratio)
|
||
{
|
||
min_ratio = r;
|
||
exit = v;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (min_ratio == DBL_MAX)
|
||
{
|
||
GTK_DEBUG (CONSTRAINTS, "Unbounded objective variable during optimization");
|
||
break;
|
||
}
|
||
|
||
#ifdef G_ENABLE_DEBUG
|
||
if (GTK_DEBUG_CHECK (CONSTRAINTS))
|
||
{
|
||
char *entry_s = gtk_constraint_variable_to_string (entry);
|
||
char *exit_s = gtk_constraint_variable_to_string (exit);
|
||
g_message ("pivot(entry: %s, exit: %s)", entry_s, exit_s);
|
||
g_free (entry_s);
|
||
g_free (exit_s);
|
||
}
|
||
#endif
|
||
|
||
gtk_constraint_solver_pivot (self, entry, exit);
|
||
}
|
||
|
||
GTK_DEBUG (CONSTRAINTS, "solver.optimize.time := %.3f ms (pass: %d)",
|
||
(float) (g_get_monotonic_time () - start_time) / 1000.f,
|
||
self->optimize_count);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_new_expression:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @constraint: a `GtkConstraintRef`
|
||
* @eplus_p: (out) (optional): the positive error variable
|
||
* @eminus_p: (out) (optional): the negative error variable
|
||
* @prev_constant_p: the constant part of the @constraint's expression
|
||
*
|
||
* Creates a new expression for the @constraint, replacing
|
||
* any basic variable with their expressions, and normalizing
|
||
* the terms to avoid a negative constant.
|
||
*
|
||
* If the @constraint is not required, this function will add
|
||
* error variables with the appropriate weight to the tableau.
|
||
*
|
||
* Returns: (transfer full): the new expression for the constraint
|
||
*/
|
||
static GtkConstraintExpression *
|
||
gtk_constraint_solver_new_expression (GtkConstraintSolver *self,
|
||
GtkConstraintRef *constraint,
|
||
GtkConstraintVariable **eplus_p,
|
||
GtkConstraintVariable **eminus_p,
|
||
double *prev_constant_p)
|
||
{
|
||
GtkConstraintExpression *cn_expr = constraint->expression;
|
||
GtkConstraintExpression *expr;
|
||
GtkConstraintExpressionIter eiter;
|
||
GtkConstraintVariable *t_v;
|
||
double t_c;
|
||
|
||
if (eplus_p != NULL)
|
||
*eplus_p = NULL;
|
||
if (eminus_p != NULL)
|
||
*eminus_p = NULL;
|
||
if (prev_constant_p != NULL)
|
||
*prev_constant_p = 0.0;
|
||
|
||
expr = gtk_constraint_expression_new (gtk_constraint_expression_get_constant (cn_expr));
|
||
|
||
gtk_constraint_expression_iter_init (&eiter, cn_expr);
|
||
while (gtk_constraint_expression_iter_next (&eiter, &t_v, &t_c))
|
||
{
|
||
GtkConstraintExpression *e = g_hash_table_lookup (self->rows, t_v);
|
||
|
||
if (e == NULL)
|
||
gtk_constraint_expression_add_variable (expr, t_v, t_c, NULL, self);
|
||
else
|
||
gtk_constraint_expression_add_expression (expr, e, t_c, NULL, self);
|
||
}
|
||
|
||
if (gtk_constraint_ref_is_inequality (constraint))
|
||
{
|
||
GtkConstraintVariable *slack_var;
|
||
|
||
/* If the constraint is an inequality, we add a slack variable to
|
||
* turn it into an equality, e.g. from
|
||
*
|
||
* expr ≥ 0
|
||
*
|
||
* to
|
||
*
|
||
* expr - slack = 0
|
||
*
|
||
* Additionally, if the constraint is not required we add an
|
||
* error variable with the weight of the constraint:
|
||
*
|
||
* expr - slack + error = 0
|
||
*/
|
||
self->slack_counter += 1;
|
||
|
||
slack_var = gtk_constraint_variable_new_slack ("s");
|
||
gtk_constraint_expression_set_variable (expr, slack_var, -1.0);
|
||
gtk_constraint_variable_unref (slack_var);
|
||
|
||
g_hash_table_insert (self->marker_vars, constraint, slack_var);
|
||
|
||
if (!gtk_constraint_ref_is_required (constraint))
|
||
{
|
||
GtkConstraintExpression *z_row;
|
||
GtkConstraintVariable *eminus;
|
||
|
||
self->slack_counter += 1;
|
||
|
||
eminus = gtk_constraint_variable_new_slack ("em");
|
||
gtk_constraint_expression_set_variable (expr, eminus, 1.0);
|
||
gtk_constraint_variable_unref (eminus);
|
||
|
||
z_row = g_hash_table_lookup (self->rows, self->objective);
|
||
gtk_constraint_expression_set_variable (z_row, eminus, constraint->strength);
|
||
|
||
gtk_constraint_solver_insert_error_variable (self, constraint, eminus);
|
||
gtk_constraint_solver_note_added_variable (self, eminus, self->objective);
|
||
gtk_constraint_variable_unref (eminus);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
GtkConstraintVariable *dummy_var;
|
||
|
||
if (gtk_constraint_ref_is_required (constraint))
|
||
{
|
||
/* If the constraint is required, we use a dummy marker variable;
|
||
* the dummy won't be allowed to enter the basis of the tableau
|
||
* when pivoting.
|
||
*/
|
||
self->dummy_counter += 1;
|
||
|
||
dummy_var = gtk_constraint_variable_new_dummy ("dummy");
|
||
|
||
if (eplus_p != NULL)
|
||
*eplus_p = dummy_var;
|
||
if (eminus_p != NULL)
|
||
*eminus_p = dummy_var;
|
||
if (prev_constant_p != NULL)
|
||
*prev_constant_p = gtk_constraint_expression_get_constant (cn_expr);
|
||
|
||
gtk_constraint_expression_set_variable (expr, dummy_var, 1.0);
|
||
g_hash_table_insert (self->marker_vars, constraint, dummy_var);
|
||
|
||
gtk_constraint_variable_unref (dummy_var);
|
||
}
|
||
else
|
||
{
|
||
GtkConstraintVariable *eplus, *eminus;
|
||
GtkConstraintExpression *z_row;
|
||
|
||
/* Since the constraint is a non-required equality, we need to
|
||
* add error variables around it, i.e. turn it from:
|
||
*
|
||
* expr = 0
|
||
*
|
||
* to:
|
||
*
|
||
* expr - eplus + eminus = 0
|
||
*/
|
||
self->slack_counter += 1;
|
||
|
||
eplus = gtk_constraint_variable_new_slack ("ep");
|
||
eminus = gtk_constraint_variable_new_slack ("em");
|
||
|
||
gtk_constraint_expression_set_variable (expr, eplus, -1.0);
|
||
gtk_constraint_expression_set_variable (expr, eminus, 1.0);
|
||
|
||
g_hash_table_insert (self->marker_vars, constraint, eplus);
|
||
|
||
z_row = g_hash_table_lookup (self->rows, self->objective);
|
||
|
||
gtk_constraint_expression_set_variable (z_row, eplus, constraint->strength);
|
||
gtk_constraint_expression_set_variable (z_row, eminus, constraint->strength);
|
||
gtk_constraint_solver_note_added_variable (self, eplus, self->objective);
|
||
gtk_constraint_solver_note_added_variable (self, eminus, self->objective);
|
||
|
||
gtk_constraint_solver_insert_error_variable (self, constraint, eplus);
|
||
gtk_constraint_solver_insert_error_variable (self, constraint, eminus);
|
||
|
||
if (constraint->is_stay)
|
||
{
|
||
g_ptr_array_add (self->stay_error_vars, gtk_constraint_variable_pair_new (eplus, eminus));
|
||
}
|
||
else if (constraint->is_edit)
|
||
{
|
||
if (eplus_p != NULL)
|
||
*eplus_p = eplus;
|
||
if (eminus_p != NULL)
|
||
*eminus_p = eminus;
|
||
if (prev_constant_p != NULL)
|
||
*prev_constant_p = gtk_constraint_expression_get_constant (cn_expr);
|
||
}
|
||
|
||
gtk_constraint_variable_unref (eplus);
|
||
gtk_constraint_variable_unref (eminus);
|
||
}
|
||
}
|
||
|
||
if (gtk_constraint_expression_get_constant (expr) < 0.0)
|
||
gtk_constraint_expression_multiply_by (expr, -1.0);
|
||
|
||
return expr;
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_dual_optimize (GtkConstraintSolver *self)
|
||
{
|
||
GtkConstraintExpression *z_row = g_hash_table_lookup (self->rows, self->objective);
|
||
#ifdef G_ENABLE_DEBUG
|
||
gint64 start_time = g_get_monotonic_time ();
|
||
#endif
|
||
|
||
/* We iterate until we don't have any more infeasible rows; the pivot()
|
||
* at the end of the loop iteration may add or remove infeasible rows
|
||
* as well
|
||
*/
|
||
while (self->infeasible_rows->len != 0)
|
||
{
|
||
GtkConstraintVariable *entry_var, *exit_var, *t_v;
|
||
GtkConstraintExpressionIter eiter;
|
||
GtkConstraintExpression *expr;
|
||
double ratio, t_c;
|
||
|
||
/* Pop the last element of the array */
|
||
exit_var = g_ptr_array_index (self->infeasible_rows, self->infeasible_rows->len - 1);
|
||
g_ptr_array_set_size (self->infeasible_rows, self->infeasible_rows->len - 1);
|
||
|
||
expr = g_hash_table_lookup (self->rows, exit_var);
|
||
if (expr == NULL)
|
||
continue;
|
||
|
||
if (gtk_constraint_expression_get_constant (expr) >= 0.0)
|
||
continue;
|
||
|
||
ratio = DBL_MAX;
|
||
entry_var = NULL;
|
||
|
||
gtk_constraint_expression_iter_init (&eiter, expr);
|
||
while (gtk_constraint_expression_iter_next (&eiter, &t_v, &t_c))
|
||
{
|
||
if (t_c > 0.0 && gtk_constraint_variable_is_pivotable (t_v))
|
||
{
|
||
double zc = gtk_constraint_expression_get_coefficient (z_row, t_v);
|
||
double r = zc / t_c;
|
||
|
||
if (r < ratio)
|
||
{
|
||
entry_var = t_v;
|
||
ratio = r;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (ratio == DBL_MAX)
|
||
g_critical ("INTERNAL: ratio == DBL_MAX in dual_optimize");
|
||
|
||
gtk_constraint_solver_pivot (self, entry_var, exit_var);
|
||
}
|
||
|
||
GTK_DEBUG (CONSTRAINTS, "dual_optimize.time := %.3f ms",
|
||
(float) (g_get_monotonic_time () - start_time) / 1000.f);
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_delta_edit_constant (GtkConstraintSolver *self,
|
||
double delta,
|
||
GtkConstraintVariable *plus_error_var,
|
||
GtkConstraintVariable *minus_error_var)
|
||
{
|
||
GtkConstraintExpression *plus_expr, *minus_expr;
|
||
GtkConstraintVariable *basic_var;
|
||
GtkConstraintVariableSet *column_set;
|
||
GtkConstraintVariableSetIter iter;
|
||
|
||
plus_expr = g_hash_table_lookup (self->rows, plus_error_var);
|
||
if (plus_expr != NULL)
|
||
{
|
||
double new_constant = gtk_constraint_expression_get_constant (plus_expr) + delta;
|
||
|
||
gtk_constraint_expression_set_constant (plus_expr, new_constant);
|
||
|
||
if (new_constant < 0.0)
|
||
g_ptr_array_add (self->infeasible_rows, plus_error_var);
|
||
|
||
return;
|
||
}
|
||
|
||
minus_expr = g_hash_table_lookup (self->rows, minus_error_var);
|
||
if (minus_expr != NULL)
|
||
{
|
||
double new_constant = gtk_constraint_expression_get_constant (minus_expr) - delta;
|
||
|
||
gtk_constraint_expression_set_constant (minus_expr, new_constant);
|
||
|
||
if (new_constant < 0.0)
|
||
g_ptr_array_add (self->infeasible_rows, minus_error_var);
|
||
|
||
return;
|
||
}
|
||
|
||
column_set = g_hash_table_lookup (self->columns, minus_error_var);
|
||
if (column_set == NULL)
|
||
{
|
||
g_critical ("INTERNAL: Columns are unset during delta edit");
|
||
return;
|
||
}
|
||
|
||
gtk_constraint_variable_set_iter_init (&iter, column_set);
|
||
while (gtk_constraint_variable_set_iter_next (&iter, &basic_var))
|
||
{
|
||
GtkConstraintExpression *expr;
|
||
double c, new_constant;
|
||
|
||
expr = g_hash_table_lookup (self->rows, basic_var);
|
||
c = gtk_constraint_expression_get_coefficient (expr, minus_error_var);
|
||
|
||
new_constant = gtk_constraint_expression_get_constant (expr) + (c * delta);
|
||
gtk_constraint_expression_set_constant (expr, new_constant);
|
||
|
||
if (gtk_constraint_variable_is_restricted (basic_var) && new_constant < 0.0)
|
||
g_ptr_array_add (self->infeasible_rows, basic_var);
|
||
}
|
||
}
|
||
|
||
static GtkConstraintVariable *
|
||
gtk_constraint_solver_choose_subject (GtkConstraintSolver *self,
|
||
GtkConstraintExpression *expression)
|
||
{
|
||
GtkConstraintExpressionIter eiter;
|
||
GtkConstraintVariable *subject = NULL;
|
||
GtkConstraintVariable *retval = NULL;
|
||
GtkConstraintVariable *t_v;
|
||
gboolean found_unrestricted = FALSE;
|
||
gboolean found_new_restricted = FALSE;
|
||
gboolean retval_found = FALSE;
|
||
double coeff = 0.0;
|
||
double t_c;
|
||
|
||
gtk_constraint_expression_iter_init (&eiter, expression);
|
||
while (gtk_constraint_expression_iter_prev (&eiter, &t_v, &t_c))
|
||
{
|
||
if (found_unrestricted)
|
||
{
|
||
if (!gtk_constraint_variable_is_restricted (t_v))
|
||
{
|
||
if (!g_hash_table_contains (self->columns, t_v))
|
||
{
|
||
retval_found = TRUE;
|
||
retval = t_v;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (gtk_constraint_variable_is_restricted (t_v))
|
||
{
|
||
if (!found_new_restricted &&
|
||
!gtk_constraint_variable_is_dummy (t_v) &&
|
||
t_c < 0.0)
|
||
{
|
||
GtkConstraintVariableSet *cset = g_hash_table_lookup (self->columns, t_v);
|
||
|
||
if (cset == NULL ||
|
||
(gtk_constraint_variable_set_is_singleton (cset) &&
|
||
g_hash_table_contains (self->columns, self->objective)))
|
||
{
|
||
subject = t_v;
|
||
found_new_restricted = TRUE;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
subject = t_v;
|
||
found_unrestricted = TRUE;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (retval_found)
|
||
return retval;
|
||
|
||
if (subject != NULL)
|
||
return subject;
|
||
|
||
gtk_constraint_expression_iter_init (&eiter, expression);
|
||
while (gtk_constraint_expression_iter_prev (&eiter, &t_v, &t_c))
|
||
{
|
||
if (!gtk_constraint_variable_is_dummy (t_v))
|
||
return NULL;
|
||
|
||
if (!g_hash_table_contains (self->columns, t_v))
|
||
{
|
||
subject = t_v;
|
||
coeff = t_c;
|
||
}
|
||
}
|
||
|
||
if (!G_APPROX_VALUE (gtk_constraint_expression_get_constant (expression), 0.0, 0.001))
|
||
{
|
||
GTK_DEBUG (CONSTRAINTS,
|
||
"Unable to satisfy required constraint (choose_subject)");
|
||
return NULL;
|
||
}
|
||
|
||
if (coeff > 0)
|
||
gtk_constraint_expression_multiply_by (expression, -1.0);
|
||
|
||
return subject;
|
||
}
|
||
|
||
static gboolean
|
||
gtk_constraint_solver_try_adding_directly (GtkConstraintSolver *self,
|
||
GtkConstraintExpression *expression)
|
||
{
|
||
GtkConstraintVariable *subject;
|
||
|
||
subject = gtk_constraint_solver_choose_subject (self, expression);
|
||
if (subject == NULL)
|
||
return FALSE;
|
||
|
||
gtk_constraint_variable_ref (subject);
|
||
|
||
gtk_constraint_expression_new_subject (expression, subject);
|
||
if (gtk_constraint_solver_column_has_key (self, subject))
|
||
gtk_constraint_solver_substitute_out (self, subject, expression);
|
||
|
||
gtk_constraint_solver_add_row (self, subject, expression);
|
||
|
||
gtk_constraint_variable_unref (subject);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_add_with_artificial_variable (GtkConstraintSolver *self,
|
||
GtkConstraintExpression *expression)
|
||
{
|
||
GtkConstraintVariable *av, *az;
|
||
GtkConstraintExpression *az_row;
|
||
GtkConstraintExpression *az_tableau_row;
|
||
GtkConstraintExpression *e;
|
||
|
||
av = gtk_constraint_variable_new_slack ("a");
|
||
self->artificial_counter += 1;
|
||
|
||
az = gtk_constraint_variable_new_objective ("az");
|
||
|
||
az_row = gtk_constraint_expression_clone (expression);
|
||
|
||
gtk_constraint_solver_add_row (self, az, az_row);
|
||
gtk_constraint_solver_add_row (self, av, expression);
|
||
|
||
gtk_constraint_expression_unref (az_row);
|
||
gtk_constraint_variable_unref (av);
|
||
gtk_constraint_variable_unref (az);
|
||
|
||
gtk_constraint_solver_optimize (self, az);
|
||
|
||
az_tableau_row = g_hash_table_lookup (self->rows, az);
|
||
if (!G_APPROX_VALUE (gtk_constraint_expression_get_constant (az_tableau_row), 0.0, 0.001))
|
||
{
|
||
gtk_constraint_solver_remove_column (self, av);
|
||
gtk_constraint_solver_remove_row (self, az, TRUE);
|
||
|
||
#ifdef G_ENABLE_DEBUG
|
||
if (GTK_DEBUG_CHECK (CONSTRAINTS))
|
||
{
|
||
char *str = gtk_constraint_expression_to_string (expression);
|
||
g_message ("Unable to satisfy a required constraint (add): %s", str);
|
||
g_free (str);
|
||
}
|
||
#endif
|
||
|
||
return;
|
||
}
|
||
|
||
e = g_hash_table_lookup (self->rows, av);
|
||
if (e != NULL)
|
||
{
|
||
GtkConstraintVariable *entry_var;
|
||
|
||
if (gtk_constraint_expression_is_constant (e))
|
||
{
|
||
gtk_constraint_solver_remove_row (self, av, TRUE);
|
||
gtk_constraint_solver_remove_row (self, az, TRUE);
|
||
return;
|
||
}
|
||
|
||
entry_var = gtk_constraint_expression_get_pivotable_variable (e);
|
||
if (entry_var == NULL)
|
||
return;
|
||
|
||
gtk_constraint_solver_pivot (self, entry_var, av);
|
||
}
|
||
|
||
g_assert (!g_hash_table_contains (self->rows, av));
|
||
|
||
gtk_constraint_solver_remove_column (self, av);
|
||
gtk_constraint_solver_remove_row (self, az, TRUE);
|
||
}
|
||
|
||
static void
|
||
gtk_constraint_solver_add_constraint_internal (GtkConstraintSolver *self,
|
||
GtkConstraintRef *constraint)
|
||
{
|
||
GtkConstraintExpression *expr;
|
||
GtkConstraintVariable *eplus;
|
||
GtkConstraintVariable *eminus;
|
||
double prev_constant;
|
||
|
||
expr = gtk_constraint_solver_new_expression (self, constraint,
|
||
&eplus,
|
||
&eminus,
|
||
&prev_constant);
|
||
|
||
#ifdef G_ENABLE_DEBUG
|
||
if (GTK_DEBUG_CHECK (CONSTRAINTS))
|
||
{
|
||
char *expr_s = gtk_constraint_expression_to_string (expr);
|
||
char *ref_s = gtk_constraint_ref_to_string (constraint);
|
||
g_message ("Adding constraint '%s' (normalized expression: '%s')", ref_s, expr_s);
|
||
g_free (ref_s);
|
||
g_free (expr_s);
|
||
}
|
||
#endif
|
||
|
||
if (constraint->is_stay)
|
||
{
|
||
StayInfo *si = g_new (StayInfo, 1);
|
||
|
||
si->constraint = constraint;
|
||
|
||
g_hash_table_insert (self->stay_var_map, constraint->variable, si);
|
||
}
|
||
else if (constraint->is_edit)
|
||
{
|
||
EditInfo *ei = g_new (EditInfo, 1);
|
||
|
||
ei->constraint = constraint;
|
||
ei->eplus = eplus;
|
||
ei->eminus = eminus;
|
||
ei->prev_constant = prev_constant;
|
||
|
||
g_hash_table_insert (self->edit_var_map, constraint->variable, ei);
|
||
}
|
||
|
||
if (!gtk_constraint_solver_try_adding_directly (self, expr))
|
||
gtk_constraint_solver_add_with_artificial_variable (self, expr);
|
||
|
||
gtk_constraint_expression_unref (expr);
|
||
|
||
self->needs_solving = TRUE;
|
||
|
||
if (self->auto_solve)
|
||
{
|
||
gtk_constraint_solver_optimize (self, self->objective);
|
||
gtk_constraint_solver_set_external_variables (self);
|
||
}
|
||
|
||
constraint->solver = self;
|
||
|
||
g_hash_table_add (self->constraints, constraint);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_new:
|
||
*
|
||
* Creates a new `GtkConstraintSolver` instance.
|
||
*
|
||
* Returns: the newly created `GtkConstraintSolver`
|
||
*/
|
||
GtkConstraintSolver *
|
||
gtk_constraint_solver_new (void)
|
||
{
|
||
return g_object_new (GTK_TYPE_CONSTRAINT_SOLVER, NULL);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_freeze:
|
||
* @solver: a `GtkConstraintSolver`
|
||
*
|
||
* Freezes the solver; any constraint addition or removal will not
|
||
* be automatically solved until gtk_constraint_solver_thaw() is
|
||
* called.
|
||
*/
|
||
void
|
||
gtk_constraint_solver_freeze (GtkConstraintSolver *solver)
|
||
{
|
||
g_return_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver));
|
||
|
||
solver->freeze_count += 1;
|
||
|
||
if (solver->freeze_count > 0)
|
||
solver->auto_solve = FALSE;
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_thaw:
|
||
* @solver: a `GtkConstraintSolver`
|
||
*
|
||
* Thaws a frozen `GtkConstraintSolver`.
|
||
*/
|
||
void
|
||
gtk_constraint_solver_thaw (GtkConstraintSolver *solver)
|
||
{
|
||
g_return_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver));
|
||
g_return_if_fail (solver->freeze_count > 0);
|
||
|
||
solver->freeze_count -= 1;
|
||
|
||
if (solver->freeze_count == 0)
|
||
{
|
||
solver->auto_solve = TRUE;
|
||
gtk_constraint_solver_resolve (solver);
|
||
}
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_note_added_variable:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @variable: a `GtkConstraintVariable`
|
||
* @subject: a `GtkConstraintVariable`
|
||
*
|
||
* Adds a new @variable into the tableau of a `GtkConstraintSolver`.
|
||
*
|
||
* This function is typically called by `GtkConstraintExpression`, and
|
||
* should never be directly called.
|
||
*/
|
||
void
|
||
gtk_constraint_solver_note_added_variable (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *variable,
|
||
GtkConstraintVariable *subject)
|
||
{
|
||
if (subject != NULL)
|
||
gtk_constraint_solver_insert_column_variable (self, variable, subject);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_note_removed_variable:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @variable: a `GtkConstraintVariable`
|
||
* @subject: a `GtkConstraintVariable`
|
||
*
|
||
* Removes a @variable from the tableau of a `GtkConstraintSolver`.
|
||
*
|
||
* This function is typically called by `GtkConstraintExpression`, and
|
||
* should never be directly called.
|
||
*/
|
||
void
|
||
gtk_constraint_solver_note_removed_variable (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *variable,
|
||
GtkConstraintVariable *subject)
|
||
{
|
||
GtkConstraintVariableSet *set;
|
||
|
||
set = g_hash_table_lookup (self->columns, variable);
|
||
if (set != NULL && subject != NULL)
|
||
gtk_constraint_variable_set_remove (set, subject);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_create_variable:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @prefix: (nullable): the prefix of the variable
|
||
* @name: (nullable): the name of the variable
|
||
* @value: the initial value of the variable
|
||
*
|
||
* Creates a new variable inside the @solver.
|
||
*
|
||
* Returns: (transfer full): the newly created variable
|
||
*/
|
||
GtkConstraintVariable *
|
||
gtk_constraint_solver_create_variable (GtkConstraintSolver *self,
|
||
const char *prefix,
|
||
const char *name,
|
||
double value)
|
||
{
|
||
GtkConstraintVariable *res;
|
||
|
||
res = gtk_constraint_variable_new (prefix, name);
|
||
gtk_constraint_variable_set_value (res, value);
|
||
|
||
self->var_counter++;
|
||
|
||
return res;
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_resolve:
|
||
* @solver: a `GtkConstraintSolver`
|
||
*
|
||
* Resolves the constraints currently stored in @solver.
|
||
*/
|
||
void
|
||
gtk_constraint_solver_resolve (GtkConstraintSolver *solver)
|
||
{
|
||
#ifdef G_ENABLE_DEBUG
|
||
gint64 start_time = g_get_monotonic_time ();
|
||
#endif
|
||
|
||
g_return_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver));
|
||
|
||
gtk_constraint_solver_dual_optimize (solver);
|
||
gtk_constraint_solver_set_external_variables (solver);
|
||
|
||
g_ptr_array_set_size (solver->infeasible_rows, 0);
|
||
|
||
gtk_constraint_solver_reset_stay_constants (solver);
|
||
|
||
GTK_DEBUG (CONSTRAINTS, "resolve.time := %.3f ms",
|
||
(float) (g_get_monotonic_time () - start_time) / 1000.f);
|
||
|
||
solver->needs_solving = FALSE;
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_add_constraint:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @variable: the subject of the constraint
|
||
* @relation: the relation of the constraint
|
||
* @expression: the expression of the constraint
|
||
* @strength: the strength of the constraint
|
||
*
|
||
* Adds a new constraint in the form of:
|
||
*
|
||
* |[
|
||
* variable relation expression (strength)
|
||
* |]
|
||
*
|
||
* into the `GtkConstraintSolver`.
|
||
*
|
||
* Returns: (transfer none): a reference to the newly created
|
||
* constraint; you can use the reference to remove the
|
||
* constraint from the solver
|
||
*/
|
||
GtkConstraintRef *
|
||
gtk_constraint_solver_add_constraint (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *variable,
|
||
GtkConstraintRelation relation,
|
||
GtkConstraintExpression *expression,
|
||
int strength)
|
||
{
|
||
GtkConstraintRef *res = g_new0 (GtkConstraintRef, 1);
|
||
|
||
res->solver = self;
|
||
res->strength = strength;
|
||
res->is_edit = FALSE;
|
||
res->is_stay = FALSE;
|
||
res->relation = relation;
|
||
|
||
if (expression == NULL)
|
||
res->expression = gtk_constraint_expression_new_from_variable (variable);
|
||
else
|
||
{
|
||
res->expression = expression;
|
||
|
||
if (variable != NULL)
|
||
{
|
||
switch (res->relation)
|
||
{
|
||
case GTK_CONSTRAINT_RELATION_EQ:
|
||
gtk_constraint_expression_add_variable (res->expression,
|
||
variable, -1.0,
|
||
NULL,
|
||
self);
|
||
break;
|
||
|
||
case GTK_CONSTRAINT_RELATION_LE:
|
||
gtk_constraint_expression_add_variable (res->expression,
|
||
variable, -1.0,
|
||
NULL,
|
||
self);
|
||
break;
|
||
|
||
case GTK_CONSTRAINT_RELATION_GE:
|
||
gtk_constraint_expression_multiply_by (res->expression, -1.0);
|
||
gtk_constraint_expression_add_variable (res->expression,
|
||
variable, 1.0,
|
||
NULL,
|
||
self);
|
||
break;
|
||
|
||
default:
|
||
g_assert_not_reached ();
|
||
}
|
||
}
|
||
}
|
||
|
||
gtk_constraint_solver_add_constraint_internal (self, res);
|
||
|
||
return res;
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_add_stay_variable:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @variable: a stay `GtkConstraintVariable`
|
||
* @strength: the strength of the constraint
|
||
*
|
||
* Adds a constraint on a stay @variable with the given @strength.
|
||
*
|
||
* A stay variable is an "anchor" in the system: a variable that is
|
||
* supposed to stay at the same value.
|
||
*
|
||
* Returns: (transfer none): a reference to the newly created
|
||
* constraint; you can use the reference to remove the
|
||
* constraint from the solver
|
||
*/
|
||
GtkConstraintRef *
|
||
gtk_constraint_solver_add_stay_variable (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *variable,
|
||
int strength)
|
||
{
|
||
GtkConstraintRef *res = g_new0 (GtkConstraintRef, 1);
|
||
|
||
res->solver = self;
|
||
res->variable = gtk_constraint_variable_ref (variable);
|
||
res->relation = GTK_CONSTRAINT_RELATION_EQ;
|
||
res->strength = strength;
|
||
res->is_stay = TRUE;
|
||
res->is_edit = FALSE;
|
||
|
||
res->expression = gtk_constraint_expression_new (gtk_constraint_variable_get_value (res->variable));
|
||
gtk_constraint_expression_add_variable (res->expression,
|
||
res->variable, -1.0,
|
||
NULL,
|
||
self);
|
||
|
||
#ifdef G_ENABLE_DEBUG
|
||
if (GTK_DEBUG_CHECK (CONSTRAINTS))
|
||
{
|
||
char *str = gtk_constraint_expression_to_string (res->expression);
|
||
g_message ("Adding stay variable: %s", str);
|
||
g_free (str);
|
||
}
|
||
#endif
|
||
|
||
gtk_constraint_solver_add_constraint_internal (self, res);
|
||
|
||
return res;
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_remove_stay_variable:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @variable: a stay variable
|
||
*
|
||
* Removes the stay constraint associated to @variable.
|
||
*
|
||
* This is a convenience function for gtk_constraint_solver_remove_constraint().
|
||
*/
|
||
void
|
||
gtk_constraint_solver_remove_stay_variable (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *variable)
|
||
{
|
||
StayInfo *si = g_hash_table_lookup (self->stay_var_map, variable);
|
||
|
||
if (si == NULL)
|
||
{
|
||
char *str = gtk_constraint_variable_to_string (variable);
|
||
|
||
g_critical ("Unknown stay variable '%s'", str);
|
||
|
||
g_free (str);
|
||
|
||
return;
|
||
}
|
||
|
||
gtk_constraint_solver_remove_constraint (self, si->constraint);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_add_edit_variable:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @variable: an edit variable
|
||
* @strength: the strength of the constraint
|
||
*
|
||
* Adds an editable constraint to the @solver.
|
||
*
|
||
* Editable constraints can be used to suggest values to a
|
||
* `GtkConstraintSolver` inside an edit phase, for instance: if
|
||
* you want to change the value of a variable without necessarily
|
||
* insert a new constraint every time.
|
||
*
|
||
* See also: gtk_constraint_solver_suggest_value()
|
||
*
|
||
* Returns: (transfer none): a reference to the newly added constraint
|
||
*/
|
||
GtkConstraintRef *
|
||
gtk_constraint_solver_add_edit_variable (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *variable,
|
||
int strength)
|
||
{
|
||
GtkConstraintRef *res = g_new0 (GtkConstraintRef, 1);
|
||
|
||
res->solver = self;
|
||
res->variable = gtk_constraint_variable_ref (variable);
|
||
res->relation = GTK_CONSTRAINT_RELATION_EQ;
|
||
res->strength = strength;
|
||
res->is_stay = FALSE;
|
||
res->is_edit = TRUE;
|
||
|
||
res->expression = gtk_constraint_expression_new (gtk_constraint_variable_get_value (variable));
|
||
gtk_constraint_expression_add_variable (res->expression,
|
||
variable, -1.0,
|
||
NULL,
|
||
self);
|
||
|
||
gtk_constraint_solver_add_constraint_internal (self, res);
|
||
|
||
return res;
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_remove_edit_variable:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @variable: an edit variable
|
||
*
|
||
* Removes the edit constraint associated to @variable.
|
||
*
|
||
* This is a convenience function around gtk_constraint_solver_remove_constraint().
|
||
*/
|
||
void
|
||
gtk_constraint_solver_remove_edit_variable (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *variable)
|
||
{
|
||
EditInfo *ei = g_hash_table_lookup (self->edit_var_map, variable);
|
||
|
||
if (ei == NULL)
|
||
{
|
||
char *str = gtk_constraint_variable_to_string (variable);
|
||
|
||
g_critical ("Unknown edit variable '%s'", str);
|
||
|
||
g_free (str);
|
||
|
||
return;
|
||
}
|
||
|
||
gtk_constraint_solver_remove_constraint (self, ei->constraint);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_remove_constraint:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @constraint: a constraint reference
|
||
*
|
||
* Removes a @constraint from the @solver.
|
||
*/
|
||
void
|
||
gtk_constraint_solver_remove_constraint (GtkConstraintSolver *self,
|
||
GtkConstraintRef *constraint)
|
||
{
|
||
GtkConstraintExpression *z_row;
|
||
GtkConstraintVariable *marker;
|
||
GtkConstraintVariableSet *error_vars;
|
||
GtkConstraintVariableSetIter iter;
|
||
|
||
if (!g_hash_table_contains (self->constraints, constraint))
|
||
return;
|
||
|
||
self->needs_solving = TRUE;
|
||
|
||
gtk_constraint_solver_reset_stay_constants (self);
|
||
|
||
z_row = g_hash_table_lookup (self->rows, self->objective);
|
||
error_vars = g_hash_table_lookup (self->error_vars, constraint);
|
||
|
||
if (error_vars != NULL)
|
||
{
|
||
GtkConstraintVariable *v;
|
||
|
||
gtk_constraint_variable_set_iter_init (&iter, error_vars);
|
||
while (gtk_constraint_variable_set_iter_next (&iter, &v))
|
||
{
|
||
GtkConstraintExpression *e = g_hash_table_lookup (self->rows, v);
|
||
|
||
if (e == NULL)
|
||
{
|
||
gtk_constraint_expression_add_variable (z_row,
|
||
v,
|
||
constraint->strength,
|
||
self->objective,
|
||
self);
|
||
}
|
||
else
|
||
{
|
||
gtk_constraint_expression_add_expression (z_row,
|
||
e,
|
||
constraint->strength,
|
||
self->objective,
|
||
self);
|
||
}
|
||
}
|
||
}
|
||
|
||
marker = g_hash_table_lookup (self->marker_vars, constraint);
|
||
if (marker == NULL)
|
||
{
|
||
g_critical ("Constraint %p not found", constraint);
|
||
return;
|
||
}
|
||
|
||
g_hash_table_remove (self->marker_vars, constraint);
|
||
|
||
if (g_hash_table_lookup (self->rows, marker) == NULL)
|
||
{
|
||
GtkConstraintVariableSet *set = g_hash_table_lookup (self->columns, marker);
|
||
GtkConstraintVariable *exit_var = NULL;
|
||
GtkConstraintVariable *v;
|
||
double min_ratio = 0;
|
||
|
||
if (set == NULL)
|
||
goto no_columns;
|
||
|
||
gtk_constraint_variable_set_iter_init (&iter, set);
|
||
while (gtk_constraint_variable_set_iter_next (&iter, &v))
|
||
{
|
||
if (gtk_constraint_variable_is_restricted (v))
|
||
{
|
||
GtkConstraintExpression *e = g_hash_table_lookup (self->rows, v);
|
||
double coeff = gtk_constraint_expression_get_coefficient (e, marker);
|
||
|
||
if (coeff < 0.0)
|
||
{
|
||
double r = -gtk_constraint_expression_get_constant (e) / coeff;
|
||
|
||
if (exit_var == NULL ||
|
||
r < min_ratio ||
|
||
G_APPROX_VALUE (r, min_ratio, 0.0001))
|
||
{
|
||
min_ratio = r;
|
||
exit_var = v;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (exit_var == NULL)
|
||
{
|
||
gtk_constraint_variable_set_iter_init (&iter, set);
|
||
while (gtk_constraint_variable_set_iter_next (&iter, &v))
|
||
{
|
||
if (gtk_constraint_variable_is_restricted (v))
|
||
{
|
||
GtkConstraintExpression *e = g_hash_table_lookup (self->rows, v);
|
||
double coeff = gtk_constraint_expression_get_coefficient (e, marker);
|
||
double r = 0.0;
|
||
|
||
if (!G_APPROX_VALUE (coeff, 0.0, 0.0001))
|
||
r = gtk_constraint_expression_get_constant (e) / coeff;
|
||
|
||
if (exit_var == NULL || r < min_ratio)
|
||
{
|
||
min_ratio = r;
|
||
exit_var = v;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (exit_var == NULL)
|
||
{
|
||
if (gtk_constraint_variable_set_is_empty (set))
|
||
gtk_constraint_solver_remove_column (self, marker);
|
||
else
|
||
{
|
||
gtk_constraint_variable_set_iter_init (&iter, set);
|
||
while (gtk_constraint_variable_set_iter_next (&iter, &v))
|
||
{
|
||
if (v != self->objective)
|
||
{
|
||
exit_var = v;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (exit_var != NULL)
|
||
gtk_constraint_solver_pivot (self, marker, exit_var);
|
||
}
|
||
|
||
no_columns:
|
||
if (g_hash_table_lookup (self->rows, marker) != NULL)
|
||
gtk_constraint_solver_remove_row (self, marker, TRUE);
|
||
else
|
||
gtk_constraint_variable_unref (marker);
|
||
|
||
if (error_vars != NULL)
|
||
{
|
||
GtkConstraintVariable *v;
|
||
|
||
gtk_constraint_variable_set_iter_init (&iter, error_vars);
|
||
while (gtk_constraint_variable_set_iter_next (&iter, &v))
|
||
{
|
||
if (v != marker)
|
||
gtk_constraint_solver_remove_column (self, v);
|
||
}
|
||
}
|
||
|
||
if (constraint->is_stay)
|
||
{
|
||
if (error_vars != NULL)
|
||
{
|
||
GPtrArray *remaining =
|
||
g_ptr_array_new_with_free_func ((GDestroyNotify) gtk_constraint_variable_pair_free);
|
||
|
||
int i = 0;
|
||
|
||
for (i = 0; i < self->stay_error_vars->len; i++)
|
||
{
|
||
GtkConstraintVariablePair *pair = g_ptr_array_index (self->stay_error_vars, i);
|
||
gboolean found = FALSE;
|
||
|
||
if (gtk_constraint_variable_set_remove (error_vars, pair->first))
|
||
found = TRUE;
|
||
|
||
if (gtk_constraint_variable_set_remove (error_vars, pair->second))
|
||
found = FALSE;
|
||
|
||
if (!found)
|
||
g_ptr_array_add (remaining, gtk_constraint_variable_pair_new (pair->first, pair->second));
|
||
}
|
||
|
||
g_clear_pointer (&self->stay_error_vars, g_ptr_array_unref);
|
||
self->stay_error_vars = remaining;
|
||
}
|
||
|
||
g_hash_table_remove (self->stay_var_map, constraint->variable);
|
||
}
|
||
else if (constraint->is_edit)
|
||
{
|
||
EditInfo *ei = g_hash_table_lookup (self->edit_var_map, constraint->variable);
|
||
|
||
gtk_constraint_solver_remove_column (self, ei->eminus);
|
||
|
||
g_hash_table_remove (self->edit_var_map, constraint->variable);
|
||
}
|
||
|
||
if (error_vars != NULL)
|
||
g_hash_table_remove (self->error_vars, constraint);
|
||
|
||
if (self->auto_solve)
|
||
{
|
||
gtk_constraint_solver_optimize (self, self->objective);
|
||
gtk_constraint_solver_set_external_variables (self);
|
||
}
|
||
|
||
g_hash_table_remove (self->constraints, constraint);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_suggest_value:
|
||
* @self: a `GtkConstraintSolver`
|
||
* @variable: a `GtkConstraintVariable`
|
||
* @value: the suggested value for @variable
|
||
*
|
||
* Suggests a new @value for an edit @variable.
|
||
*
|
||
* The @variable must be an edit variable, and the solver must be
|
||
* in an edit phase.
|
||
*/
|
||
void
|
||
gtk_constraint_solver_suggest_value (GtkConstraintSolver *self,
|
||
GtkConstraintVariable *variable,
|
||
double value)
|
||
{
|
||
EditInfo *ei = g_hash_table_lookup (self->edit_var_map, variable);
|
||
double delta;
|
||
if (ei == NULL)
|
||
{
|
||
g_critical ("Suggesting value '%g' but variable %p is not editable",
|
||
value, variable);
|
||
return;
|
||
}
|
||
|
||
if (!self->in_edit_phase)
|
||
{
|
||
g_critical ("Suggesting value '%g' for variable '%p' but solver is "
|
||
"not in an edit phase",
|
||
value, variable);
|
||
return;
|
||
}
|
||
|
||
delta = value - ei->prev_constant;
|
||
ei->prev_constant = value;
|
||
|
||
gtk_constraint_solver_delta_edit_constant (self, delta, ei->eplus, ei->eminus);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_has_stay_variable:
|
||
* @solver: a `GtkConstraintSolver`
|
||
* @variable: a `GtkConstraintVariable`
|
||
*
|
||
* Checks whether @variable is a stay variable.
|
||
*
|
||
* Returns: %TRUE if the variable is a stay variable
|
||
*/
|
||
gboolean
|
||
gtk_constraint_solver_has_stay_variable (GtkConstraintSolver *solver,
|
||
GtkConstraintVariable *variable)
|
||
{
|
||
g_return_val_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver), FALSE);
|
||
g_return_val_if_fail (variable != NULL, FALSE);
|
||
|
||
return g_hash_table_contains (solver->stay_var_map, variable);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_has_edit_variable:
|
||
* @solver: a `GtkConstraintSolver`
|
||
* @variable: a `GtkConstraintVariable`
|
||
*
|
||
* Checks whether @variable is an edit variable.
|
||
*
|
||
* Returns: %TRUE if the variable is an edit variable
|
||
*/
|
||
gboolean
|
||
gtk_constraint_solver_has_edit_variable (GtkConstraintSolver *solver,
|
||
GtkConstraintVariable *variable)
|
||
{
|
||
g_return_val_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver), FALSE);
|
||
g_return_val_if_fail (variable != NULL, FALSE);
|
||
|
||
return g_hash_table_contains (solver->edit_var_map, variable);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_begin_edit:
|
||
* @solver: a `GtkConstraintSolver`
|
||
*
|
||
* Begins the edit phase for a constraint system.
|
||
*
|
||
* Typically, you need to add new edit constraints for a variable to the
|
||
* system, using gtk_constraint_solver_add_edit_variable(); then you
|
||
* call this function and suggest values for the edit variables, using
|
||
* gtk_constraint_solver_suggest_value(). After you suggested a value
|
||
* for all the variables you need to edit, you will need to call
|
||
* gtk_constraint_solver_resolve() to solve the system, and get the value
|
||
* of the various variables that you're interested in.
|
||
*
|
||
* Once you completed the edit phase, call gtk_constraint_solver_end_edit()
|
||
* to remove all the edit variables.
|
||
*/
|
||
void
|
||
gtk_constraint_solver_begin_edit (GtkConstraintSolver *solver)
|
||
{
|
||
g_return_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver));
|
||
|
||
if (g_hash_table_size (solver->edit_var_map) == 0)
|
||
{
|
||
g_critical ("Solver %p does not have editable variables.", solver);
|
||
return;
|
||
}
|
||
|
||
g_ptr_array_set_size (solver->infeasible_rows, 0);
|
||
gtk_constraint_solver_reset_stay_constants (solver);
|
||
|
||
solver->in_edit_phase = TRUE;
|
||
}
|
||
|
||
static void
|
||
edit_info_free (gpointer data)
|
||
{
|
||
g_free (data);
|
||
}
|
||
|
||
/*< private >
|
||
* gtk_constraint_solver_end_edit:
|
||
* @solver: a `GtkConstraintSolver`
|
||
*
|
||
* Ends the edit phase for a constraint system, and clears
|
||
* all the edit variables introduced.
|
||
*/
|
||
void
|
||
gtk_constraint_solver_end_edit (GtkConstraintSolver *solver)
|
||
{
|
||
solver->in_edit_phase = FALSE;
|
||
|
||
gtk_constraint_solver_resolve (solver);
|
||
|
||
g_hash_table_remove_all (solver->edit_var_map);
|
||
}
|
||
|
||
void
|
||
gtk_constraint_solver_clear (GtkConstraintSolver *solver)
|
||
{
|
||
g_return_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver));
|
||
|
||
g_hash_table_remove_all (solver->constraints);
|
||
g_hash_table_remove_all (solver->external_rows);
|
||
g_hash_table_remove_all (solver->external_parametric_vars);
|
||
g_hash_table_remove_all (solver->error_vars);
|
||
g_hash_table_remove_all (solver->marker_vars);
|
||
g_hash_table_remove_all (solver->edit_var_map);
|
||
g_hash_table_remove_all (solver->stay_var_map);
|
||
|
||
g_ptr_array_set_size (solver->infeasible_rows, 0);
|
||
g_ptr_array_set_size (solver->stay_error_vars, 0);
|
||
|
||
g_hash_table_remove_all (solver->rows);
|
||
g_hash_table_remove_all (solver->columns);
|
||
|
||
/* The rows table owns the objective variable */
|
||
solver->objective = gtk_constraint_variable_new_objective ("Z");
|
||
g_hash_table_insert (solver->rows,
|
||
solver->objective,
|
||
gtk_constraint_expression_new (0.0));
|
||
|
||
solver->slack_counter = 0;
|
||
solver->dummy_counter = 0;
|
||
solver->artificial_counter = 0;
|
||
solver->freeze_count = 0;
|
||
|
||
solver->needs_solving = FALSE;
|
||
solver->auto_solve = TRUE;
|
||
}
|
||
|
||
char *
|
||
gtk_constraint_solver_to_string (GtkConstraintSolver *solver)
|
||
{
|
||
GString *buf = g_string_new (NULL);
|
||
|
||
g_string_append (buf, "Tableau info:\n");
|
||
g_string_append_printf (buf, "Rows: %d (= %d constraints)\n",
|
||
g_hash_table_size (solver->rows),
|
||
g_hash_table_size (solver->rows) - 1);
|
||
g_string_append_printf (buf, "Columns: %d\n",
|
||
g_hash_table_size (solver->columns));
|
||
g_string_append_printf (buf, "Infeasible rows: %d\n",
|
||
solver->infeasible_rows->len);
|
||
g_string_append_printf (buf, "External basic variables: %d\n",
|
||
g_hash_table_size (solver->external_rows));
|
||
g_string_append_printf (buf, "External parametric variables: %d\n",
|
||
g_hash_table_size (solver->external_parametric_vars));
|
||
|
||
g_string_append (buf, "Constraints:");
|
||
if (g_hash_table_size (solver->constraints) == 0)
|
||
g_string_append (buf, " <empty>\n");
|
||
else
|
||
{
|
||
GHashTableIter iter;
|
||
gpointer key_p;
|
||
|
||
g_string_append (buf, "\n");
|
||
|
||
g_hash_table_iter_init (&iter, solver->constraints);
|
||
while (g_hash_table_iter_next (&iter, &key_p, NULL))
|
||
{
|
||
char *ref = gtk_constraint_ref_to_string (key_p);
|
||
|
||
g_string_append_printf (buf, " %s\n", ref);
|
||
|
||
g_free (ref);
|
||
}
|
||
}
|
||
|
||
g_string_append (buf, "Stay error vars:");
|
||
if (solver->stay_error_vars->len == 0)
|
||
g_string_append (buf, " <empty>\n");
|
||
else
|
||
{
|
||
g_string_append (buf, "\n");
|
||
|
||
for (int i = 0; i < solver->stay_error_vars->len; i++)
|
||
{
|
||
const GtkConstraintVariablePair *pair = g_ptr_array_index (solver->stay_error_vars, i);
|
||
char *first_s = gtk_constraint_variable_to_string (pair->first);
|
||
char *second_s = gtk_constraint_variable_to_string (pair->second);
|
||
|
||
g_string_append_printf (buf, " (%s, %s)\n", first_s, second_s);
|
||
|
||
g_free (first_s);
|
||
g_free (second_s);
|
||
}
|
||
}
|
||
|
||
g_string_append (buf, "Edit var map:");
|
||
if (g_hash_table_size (solver->edit_var_map) == 0)
|
||
g_string_append (buf, " <empty>\n");
|
||
else
|
||
{
|
||
GHashTableIter iter;
|
||
gpointer key_p, value_p;
|
||
|
||
g_string_append (buf, "\n");
|
||
|
||
g_hash_table_iter_init (&iter, solver->edit_var_map);
|
||
while (g_hash_table_iter_next (&iter, &key_p, &value_p))
|
||
{
|
||
char *var = gtk_constraint_variable_to_string (key_p);
|
||
const EditInfo *ei = value_p;
|
||
char *c = gtk_constraint_ref_to_string (ei->constraint);
|
||
|
||
g_string_append_printf (buf, " %s => %s\n", var, c);
|
||
|
||
g_free (var);
|
||
g_free (c);
|
||
}
|
||
}
|
||
|
||
return g_string_free (buf, FALSE);
|
||
}
|
||
|
||
char *
|
||
gtk_constraint_solver_statistics (GtkConstraintSolver *solver)
|
||
{
|
||
GString *buf = g_string_new (NULL);
|
||
|
||
g_string_append_printf (buf, "Variables: %d\n", solver->var_counter);
|
||
g_string_append_printf (buf, "Slack vars: %d\n", solver->slack_counter);
|
||
g_string_append_printf (buf, "Artificial vars: %d\n", solver->artificial_counter);
|
||
g_string_append_printf (buf, "Dummy vars: %d\n", solver->dummy_counter);
|
||
g_string_append_printf (buf, "Stay vars: %d\n", g_hash_table_size (solver->stay_var_map));
|
||
g_string_append_printf (buf, "Optimize count: %d\n", solver->optimize_count);
|
||
g_string_append_printf (buf, "Rows: %d\n", g_hash_table_size (solver->rows));
|
||
g_string_append_printf (buf, "Columns: %d\n", g_hash_table_size (solver->columns));
|
||
|
||
if (g_hash_table_size (solver->columns) > 0)
|
||
{
|
||
GHashTableIter iter;
|
||
gpointer val;
|
||
double sum = 0;
|
||
|
||
g_hash_table_iter_init (&iter, solver->columns);
|
||
while (g_hash_table_iter_next (&iter, NULL, &val))
|
||
{
|
||
GtkConstraintVariableSet *set = val;
|
||
sum += gtk_constraint_variable_set_size (set);
|
||
}
|
||
g_string_append_printf (buf, "Avg column size: %g\n", sum / g_hash_table_size (solver->columns));
|
||
}
|
||
|
||
g_string_append_printf (buf, "Infeasible rows: %d\n", solver->infeasible_rows->len);
|
||
g_string_append_printf (buf, "External basic variables: %d\n", g_hash_table_size (solver->external_rows));
|
||
g_string_append_printf (buf, "External parametric variables: %d\n", g_hash_table_size (solver->external_parametric_vars));
|
||
|
||
return g_string_free (buf, FALSE);
|
||
}
|