% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/tsbalancing.R
\name{tsbalancing}
\alias{tsbalancing}
\title{Restore cross-sectional (contemporaneous) linear constraints}
\usage{
tsbalancing(
  in_ts,
  problem_specs_df,
  temporal_grp_periodicity = 1,
  temporal_grp_start = 1,
  osqp_settings_df = default_osqp_sequence,
  display_level = 1,
  alter_pos = 1,
  alter_neg = 1,
  alter_mix = 1,
  alter_temporal = 0,
  lower_bound = -Inf,
  upper_bound = Inf,
  tolV = 0,
  tolV_temporal = 0,
  tolP_temporal = NA,

  # New in G-Series 3.0
  validation_tol = 0.001,
  trunc_to_zero_tol = validation_tol,
  full_sequence = FALSE,
  validation_only = FALSE,
  quiet = FALSE
)
}
\arguments{
\item{in_ts}{(mandatory)

Time series (object of class "ts" or "mts") that contains the time series data to be reconciled.
They are the balancing problems' input data (initial solutions).}

\item{problem_specs_df}{(mandatory)

Balancing problem specifications data frame (object of class "data.frame"). Using a sparse format inspired from the
SAS/OR\eqn{^\circledR}{®} LP procedure’s \emph{sparse data input format} (SAS Institute 2015), it contains only the relevant
information such as the nonzero coefficients of the balancing constraints as well as the non-default alterability
coefficients and lower/upper bounds (i.e., values that would take precedence over those defined with arguments \code{alter_pos},
\code{alter_neg}, \code{alter_mix}, \code{alter_temporal}, \code{lower_bound} and \code{upper_bound}).

The information is provided using four mandatory variables (\code{type}, \code{col}, \code{row} and \code{coef}) and one optional variable
(\code{timeVal}). An observation (a row) in the problem specs data frame either defines a label for one of the seven types of the
balancing problem elements with columns \code{type} and \code{row} (see \emph{Label definition records} below) or specifies coefficients
(numerical values) for those balancing problem elements with variables \code{col}, \code{row}, \code{coef} and \code{timeVal} (see \emph{Information
specification records} below).
\itemize{
\item \strong{Label definition records} (\code{type} is not missing (is not \code{NA}))
\itemize{
\item \code{type} (chr): reserved keyword identifying the type of problem element being defined:
\itemize{
\item \code{EQ}: equality (\eqn{=}) balancing constraint
\item \code{LE}: lower or equal (\eqn{\le}{<=}) balancing constraint
\item \code{GE}: greater or equal (\eqn{\ge}{>=}) balancing constraint
\item \code{lowerBd}: period value lower bound
\item \code{upperBd}: period value upper bound
\item \code{alter}: period values alterability coefficient
\item \code{alterTmp}: temporal total alterability coefficient
}
\item \code{row} (chr): label to be associated to the problem element (\emph{\code{type} keyword})
\item \emph{all other variables are irrelevant and should contain missing data (\code{NA} values)} \cr \cr
}
\item \strong{Information specification records} (\code{type} is missing (is \code{NA}))
\itemize{
\item \code{type} (chr): not applicable (\code{NA})
\item \code{col} (chr): series name or reserved word \verb{_rhs_} to specify a balancing constraint right-hand side (RHS) value.
\item \code{row} (chr): problem element label.
\item \code{coef} (num): problem element value:
\itemize{
\item balancing constraint series coefficient or RHS value
\item series period value lower or upper bound
\item series period value or temporal total alterability coefficient
}
\item \code{timeVal} (num): optional time value to restrict the application of series bounds or alterability coefficients
to a specific time period (or temporal group). It corresponds to the time value, as returned by \code{stats::time()}, of a given
input time series (argument \code{in_ts}) period (observation) and is conceptually equivalent to \eqn{year + (period - 1) / 
  frequency}.
}
}

Note that empty strings (\code{""} or \code{''}) for character variables are interpreted as missing (\code{NA}) by the function. Variable
\code{row} identifies the elements of the balancing problem and is the key variable that makes the link between both types of
records. The same label (\code{row}) cannot be associated with more than one type of problem element (\code{type}) and multiple labels
(\code{row}) cannot be defined for the same given type of problem element (\code{type}), except for balancing constraints (values
\code{"EQ"}, \code{"LE"} and \code{"GE"} of column \code{type}). User-friendly features of the problem specs data frame include:
\itemize{
\item The order of the observations (rows) is not important.
\item Character values (variables \code{type}, \code{row} and \code{col}) are not case sensitive (e.g., strings \code{"Constraint 1"} and
\code{"CONSTRAINT 1"} for \code{row} would be considered as the same problem element label), except when \code{col} is used to specify a
series name (a column of the input time series object) where \strong{case sensitivity is enforced}.
\item The variable names of the problem specs data frame are also not case sensitive (e.g., \code{type}, \code{Type} or \code{TYPE} are all
valid) and \code{time_val} is an accepted variable name (instead of \code{timeVal}).
}

Finally, the following table lists valid aliases for the \emph{\code{type} keywords} (type of problem element):\tabular{cl}{
   \strong{Keyword} \tab \strong{Aliases} \cr
   \code{EQ} \tab \code{==}, \code{=} \cr
   \code{LE} \tab \code{<=}, \code{<} \cr
   \code{GE} \tab \code{>=}, \code{>} \cr
   \code{lowerBd} \tab \code{lowerBound}, \code{lowerBnd}, + \emph{same terms with '_', '.' or ' ' between words} \cr
   \code{upperBd} \tab \code{upperBound}, \code{upperBnd}, + \emph{same terms with '_', '.' or ' ' between words} \cr
   \code{alterTmp} \tab \code{alterTemporal}, \code{alterTemp}, + \emph{same terms with '_', '.' or ' ' between words} \cr
}


Reviewing the \strong{Examples} should help conceptualize the balancing problem specifications data frame.}

\item{temporal_grp_periodicity}{(optional)

Positive integer defining the number of periods in temporal groups for which the totals should be preserved.
E.g., specify \code{temporal_grp_periodicity = 3} with a monthly time series for quarterly total preservation and
\code{temporal_grp_periodicity = 12} (or \code{temporal_grp_periodicity = frequency(in_ts)}) for annual total preservation.
Specifying \code{temporal_grp_periodicity = 1} (\emph{default}) corresponds to period-by-period processing without
temporal total preservation.

\strong{Default value} is \code{temporal_grp_periodicity = 1} (period-by-period processing without temporal total preservation).}

\item{temporal_grp_start}{(optional)

Integer in the [1 .. \code{temporal_grp_periodicity}] interval specifying the starting period (cycle) for temporal
total preservation. E.g., annual totals corresponding to fiscal years defined from April to March of the
following year would be specified with \code{temporal_grp_start = 4} for a monthly time series (\code{frequency(in_ts) = 12})
and \code{temporal_grp_start = 2} for a quarterly time series (\code{frequency(in_ts) = 4}). This argument has no effect
for period-by-period processing without temporal total preservation (\code{temporal_grp_periodicity = 1}).

\strong{Default value} is \code{temporal_grp_start = 1}.}

\item{osqp_settings_df}{(optional)

Data frame (object of class "data.frame") containing a sequence of OSQP settings for solving the balancing problems.
The package includes two predefined OSQP settings sequence data frames:
\itemize{
\item \link{default_osqp_sequence}: fast and effective (default);
\item \link{alternate_osqp_sequence}: geared towards precision at the expense of execution time.
}

See \code{vignette("osqp-settings-sequence-dataframe")} for more details on this topic and to see the actual contents
of these two data frames. Note that the concept of a \emph{solving sequence} with different sets of solver settings is
new in G-Series 3.0 (a single solving attempt was made in G-Series 2.0).

\strong{Default value} is \code{osqp_settings_df = default_osqp_sequence}.}

\item{display_level}{(optional)

Integer in the [0 .. 3] interval specifying the level of information to display in the console (\code{stdout()}).
Note that specifying argument \code{quiet = TRUE} would \emph{nullify} argument \code{display_level} (none of the following information
would be displayed).\tabular{lcccc}{
   \strong{Displayed information} \tab \strong{\code{0}} \tab \strong{\code{1}} \tab \strong{\code{2}} \tab \strong{\code{3}} \cr
   Function header \tab \eqn{\checkmark}{Y} \tab \eqn{\checkmark}{Y} \tab \eqn{\checkmark}{Y} \tab \eqn{\checkmark}{Y} \cr
   Balancing problem elements \tab  \tab \eqn{\checkmark}{Y} \tab \eqn{\checkmark}{Y} \tab \eqn{\checkmark}{Y} \cr
   Individual problem solving details \tab  \tab  \tab \eqn{\checkmark}{Y} \tab \eqn{\checkmark}{Y} \cr
   Individual problem results (values and constraints) \tab  \tab  \tab  \tab \eqn{\checkmark}{Y} \cr
}


\strong{Default value} is \code{display_level = 1}.}

\item{alter_pos}{(optional)

Nonnegative real number specifying the default alterability coefficient associated to the values of time series with \strong{positive}
coefficients in all balancing constraints in which they are involved (e.g., component series in aggregation table raking problems).
Alterability coefficients provided in the problem specification data frame (argument \code{problem_specs_df}) override this value.

\strong{Default value} is \code{alter_pos = 1.0} (nonbinding values).}

\item{alter_neg}{(optional)

Nonnegative real number specifying the default alterability coefficient associated to the values of time series with \strong{negative}
coefficients in all balancing constraints in which they are involved (e.g., marginal totals in aggregation table raking problems).
Alterability coefficients provided in the problem specification data frame (argument \code{problem_specs_df}) override this value.

\strong{Default value} is \code{alter_neg = 1.0} (nonbinding values).}

\item{alter_mix}{(optional)

Nonnegative real number specifying the default alterability coefficient associated to the values of time series with a mix of
\strong{positive and negative} coefficients in the balancing constraints in which they are involved. Alterability coefficients provided
in the problem specification data frame (argument \code{problem_specs_df}) override this value.

\strong{Default value} is \code{alter_mix = 1.0} (nonbinding values).}

\item{alter_temporal}{(optional)

Nonnegative real number specifying the default alterability coefficient associated to the time series temporal totals.
Alterability coefficients provided in the problem specification data frame (argument \code{problem_specs_df}) override this value.

\strong{Default value} is \code{alter_temporal = 0.0} (binding values).}

\item{lower_bound}{(optional)

Real number specifying the default lower bound for the time series values. Lower bounds provided in the problem specification
data frame (argument \ifelse{latex}{\code{problem _specs_df}}{\code{problem_specs_df}}) override this value.

\strong{Default value} is \code{lower_bound = -Inf} (unbounded).}

\item{upper_bound}{(optional)

Real number specifying the default upper bound for the time series values. Upper bounds provided in the problem specification
data frame (argument \ifelse{latex}{\code{problem _specs_df}}{\code{problem_specs_df}}) override this value.

\strong{Default value} is \code{upper_bound = Inf} (unbounded).}

\item{tolV}{(optional)

Nonnegative real number specifying the tolerance, in absolute value, for the balancing constraints right-hand side (RHS)
values:
\itemize{
\item \code{EQ} constraints: \eqn{\quad A\mathbf{x} = \mathbf{b} \quad}{Ax = b} become \eqn{\quad \mathbf{b} - \epsilon \le A\mathbf{x} 
\le \mathbf{b} + \epsilon}{b - eps <= Ax <= b + eps}
\item \code{LE} constraints: \eqn{\quad A\mathbf{x} \le \mathbf{b} \quad}{Ax <= b} become \eqn{\quad A\mathbf{x} \le \mathbf{b} + 
\epsilon}{Ax <= b + eps}
\item \code{GE} constraints: \eqn{\quad A\mathbf{x} \ge \mathbf{b} \quad}{Ax >= b} become \eqn{\quad A\mathbf{x} \ge \mathbf{b} - 
\epsilon}{Ax >= b - eps}
}

where \eqn{\epsilon}{eps} is the tolerance specified with \code{tolV}. This argument does not apply to the \emph{period value (lower
and upper) bounds} specified with arguments \code{lower_bound} and \code{upper_bound} or in the problem specs data frame (argument
\code{prob_specs_df}). I.e., \code{tolV} does not affect the time series values lower and upper bounds, unless they are specified as
\emph{balancing constraints} instead (with \code{GE} and \code{LE} constraints in the problem specs data frame).

\strong{Default value} is \code{tolV = 0.0} (no tolerance).}

\item{tolV_temporal, tolP_temporal}{(optional)

Nonnegative real number, or \code{NA}, specifying the tolerance, in percentage (\code{tolP_temporal}) or absolute value
(\code{tolV_temporal}), for the implicit temporal aggregation constraints associated to \strong{binding temporal totals}
\eqn{\left( \sum_t{x_{i,t}} = \sum_t{y_{i,t}} \right)}{(sum_t{x_{i,t}} = sum_t{y_{i,t}})}, which become:
\deqn{\sum_t{y_{i,t}} - \epsilon_\text{abs} \le \sum_t{x_{i,t}} \le \sum_t{y_{i,t}} + \epsilon_\text{abs}}{sum_t{y_{i,t}} - 
eps_abs <= sum_t{x_{i,t}} <= sum_t{y_{i,t}} + eps_abs}
or
\deqn{\sum_t{y_{i,t}} \left( 1 - \epsilon_\text{rel} \right) \le \sum_t{x_{i,t}} \le \sum_t{y_{i,t}} \left( 1 + 
\epsilon_\text{rel} \right)}{sum_t{y_{i,t}} (1 - eps_rel) <= sum_t{x_{i,t}} <= sum_t{y_{i,t}} (1 + eps_rel)}

where \eqn{\epsilon_\text{abs}}{eps_abs} and \eqn{\epsilon_\text{rel}}{eps_rel} are the absolute and percentage tolerances
specified respectively with \code{tolV_temporal} and  \code{tolP_temporal}. Both arguments cannot be specified together (one must be
specified while the other must be \code{NA}).

\strong{Example:} to set a tolerance of 10 \emph{units}, specify \ifelse{latex}{\code{tolV_temporal = 10, tolP_temporal} \code{= NA}}{
\code{tolV_temporal = 10, tolP_temporal = NA}}; to set a tolerance of 1\%, specify\verb{tolV_temporal = NA, tolP_temporal = 0.01}.

\strong{Default values} are \code{tolV_temporal = 0.0} and \code{tolP_temporal = NA} (no tolerance).}

\item{validation_tol}{(optional)

Nonnegative real number specifying the tolerance for the validation of the balancing results. The function verifies if
the final (reconciled) time series values meet the constraints, allowing for discrepancies up to the value specified with
this argument. A warning is issued as soon as one constraint is not met (discrepancy greater than \code{validation_tol}).

With constraints defined as \eqn{\mathbf{l} \le A\mathbf{x} \le \mathbf{u}}{l <= Ax <= u}, where \eqn{\mathbf{l = u}}{l = u}
for \code{EQ} constraints, \eqn{\mathbf{l} = -\infty}{l = -Inf} for \code{LE} constraints and \eqn{\mathbf{u} = \infty}{u = Inf} for
\code{GE} constraints, \strong{constraint discrepancies} correspond to \eqn{\max \left( 0, \mathbf{l} - A\mathbf{x}, A\mathbf{x} - 
\mathbf{u} \right)}{max(0, l - Ax, Ax - u)}, where constraint bounds \eqn{\mathbf{l}}{l} and \eqn{\mathbf{u}}{u} include
the tolerances, when applicable, specified with arguments \code{tolV}, \code{tolV_temporal} and \code{tolP_temporal}.

\strong{Default value} is \code{validation_tol = 0.001}.}

\item{trunc_to_zero_tol}{(optional)

Nonnegative real number specifying the tolerance, in absolute value, for replacing by zero (small) values in the output
(reconciled) time series data (output object \code{out_ts}). Specify \code{trunc_to_zero_tol = 0} to disable this \emph{truncation to zero}
process on the reconciled data. Otherwise, specify \code{trunc_to_zero_tol > 0} to replace with \eqn{0.0} any value in the
\eqn{\left[ -\epsilon, \epsilon \right]}{[-eps, eps]} interval, where \eqn{\epsilon}{eps} is the tolerance specified with
\code{trunc_to_zero_tol}.

Note that the final constraint discrepancies (see argument \code{validation_tol}) are calculated on the \emph{zero truncated}
reconciled time series values, therefore ensuring accurate validation of the actual reconciled data returned by the
function.

\strong{Default value} is \code{trunc_to_zero_tol = validation_tol}.}

\item{full_sequence}{(optional)

Logical argument specifying whether all the steps of the \emph{OSQP settings sequence data frame} should be performed or not.
See argument \code{osqp_settings_df} and \code{vignette("osqp-settings-sequence-dataframe")} for more details on this topic.

\strong{Default value} is \code{full_sequence = FALSE}.}

\item{validation_only}{(optional)

Logical argument specifying whether the function should only perform input data validation or not. When
\code{validation_only = TRUE}, the specified \emph{balancing constraints} and \emph{period value (lower and upper) bounds} constraints
are validated against the input time series data, allowing for discrepancies up to the value specified with argument
\code{validation_tol}. Otherwise, when \code{validation_only = FALSE} (default), the input data are first reconciled and the
resulting (output) data are then validated.

\strong{Default value} is \code{validation_only = FALSE}.}

\item{quiet}{(optional)

Logical argument specifying whether or not to display only essential information such as warnings, errors and the period
(or set of periods) being reconciled. You could further suppress, if desired, the display of the \emph{balancing period(s)}
information by \emph{wrapping} your \code{\link[=tsbalancing]{tsbalancing()}} call with \code{\link[=suppressMessages]{suppressMessages()}}. In that case, the \code{proc_grp_df} output
data frame can be used to identify (unsuccessful) balancing problems associated with warning messages (if any). Note that
specifying \code{quiet = TRUE} would also \emph{nullify} argument \code{display_level}.

\strong{Default value} is \code{quiet = FALSE}.}
}
\value{
The function returns is a list of seven objects:
\itemize{
\item \code{out_ts}: modified version of the input time series object (class "ts" or "mts"; see argument \code{in_ts}) with the resulting
reconciled time series values (primary function output). It can be explicitly coerced to another type of object with the
appropriate \verb{as*()} function (e.g., \code{tsibble::as_tsibble()} would coerce it to a tsibble).
\item \code{proc_grp_df}: processing group summary data frame, useful to identify problems that have succeeded or failed.
It contains one observation (row) for each balancing problem with the following columns:
\itemize{
\item \code{proc_grp} (num): processing group id.
\item \code{proc_grp_type} (chr): processing group type. Possible values are:
\itemize{
\item \code{"period"};
\item \code{"temporal group"}.
}
\item \code{proc_grp_label} (chr): string describing the processing group in the following format:
\itemize{
\item \code{"<year>-<period>"} (single periods)
\item \code{"<start year>-<start period> - <end year>-<end period>"} (temporal groups)
}
\item \code{sol_status_val}, \code{sol_status} (num, chr): solution status numerical (integer) value and description string:
\itemize{
\item \code{ 1}: \code{"valid initial solution"};
\item \code{-1}: \code{"invalid initial solution"};
\item \code{ 2}: \code{"valid polished osqp solution"};
\item \code{-2}: \code{"invalid polished osqp solution"};
\item \code{ 3}: \code{"valid unpolished osqp solution"};
\item \code{-3}: \code{"invalid unpolished osqp solution"};
\item \code{-4}: \code{"unsolvable fixed problem"} (invalid initial solution).
}
\item \code{n_unmet_con} (num): number of unmet constraints (\code{sum(prob_conf_df$unmet_flag)}).
\item \code{max_discr} (num): maximum constraint discrepancy (\code{max(prob_conf_df$discr_out)}).
\item \code{validation_tol} (num): specified tolerance for validation purposes (argument \code{validation_tol}).
\item \code{sol_type} (chr): returned solution type. Possible values are:
\itemize{
\item \code{"initial"} (initial solution, i.e., input data values);
\item \code{"osqp"} (OSQP solution).
}
\item \code{osqp_attempts} (num): number of attempts made with OSQP (depth achieved in the solving sequence).
\item \code{osqp_seqno} (num): step # of the solving sequence corresponding to the returned solution. \code{NA} when
\code{sol_type = "initial"}.
\item \code{osqp_status} (chr): OSQP status description string (\code{osqp_sol_info_df$status}). \code{NA} when \code{sol_type = "initial"}.
\item \code{osqp_polished} (logi): \code{TRUE} if the returned OSQP solution is polished (\ifelse{latex}{\code{osqp_sol_info_df 
  $status_polish = 1}}{\code{osqp_sol_info_df$status_polish = 1}}), \code{FALSE} otherwise. \code{NA} when \code{sol_type = "initial"}.
\item \code{total_solve_time} (num): total time, in seconds, of the solving sequence.
}

Column \code{proc_grp} constitutes a \emph{unique key} (distinct rows) for the data frame. Successful balancing problems
(problems with a valid solution) correspond to rows with \code{sol_status_val > 0} or, equivalently, to \code{n_unmet_con = 0}
or to \code{max_discr <= validation_tol}. The \emph{initial solution} (\code{sol_type = "initial"}) is returned only if \strong{a)}
there are no initial constraint discrepancies, \strong{b)} the problem is fixed (all values are binding) or \strong{c)} it beats
the OSQP solution (smaller total constraint discrepancies). The OSQP solving sequence is described in \ifelse{latex}{
  \code{vignette("osqp-settings -sequence-dataframe")}}{\code{vignette("osqp-settings-sequence-dataframe")}}.
\item \code{periods_df}: time periods data frame, useful to match periods to processing groups. It contains one observation
(row) for each period of the input time series object (argument \code{in_ts}) with the following columns:
\itemize{
\item \code{proc_grp} (num): processing group id.
\item \code{t} (num): time id (\code{1:nrow(in_ts)}).
\item \code{time_val} (num): time value (\code{stats::time(in_ts)}). It conceptually corresponds to \eqn{year + (period - 1) / frequency}.
}

Columns \code{t} and \code{time_val} both constitute a \emph{unique key} (distinct rows) for the data frame.
\item \code{prob_val_df}: problem values data frame, useful to analyze change diagnostics, i.e., initial vs final (reconciled)
values. It contains one observation (row) for each value involved in each balancing problem, with the following columns:
\itemize{
\item \code{proc_grp} (num): processing group id.
\item \code{val_type} (chr): problem value type. Possible values are:
\itemize{
\item \code{"period value"};
\item \code{"temporal total"}.
}
\item \code{name} (chr): time series (variable) name.
\item \code{t} (num): time id (\code{1:nrow(in_ts)}); id of the first period of the temporal group for a \emph{temporal total}.
\item \code{time_val} (num): time value (\code{stats::time(in_ts)}); value of the first period of the temporal group for a
\emph{temporal total}. It conceptually corresponds to \eqn{year + (period - 1) / frequency}.
\item \code{lower_bd}, \code{upper_bd} (num): period value bounds; always \code{-Inf} and \code{Inf} for a \emph{temporal total}.
\item \code{alter} (num): alterability coefficient.
\item \code{value_in}, \code{value_out} (num): initial and final (reconciled) values.
\item \code{dif} (num): \code{value_out - value_in}.
\item \code{rdif} (num): \code{dif / value_in}; \code{NA} if \code{value_in = 0}.
}

Columns \code{val_type + name + t} and \code{val_type + name + time_val} both constitute a \emph{unique key} (distinct rows) for the
data frame. Binding (fixed) problem values correspond to rows with \code{alter = 0} or \code{value_in = 0}. Conversely, nonbinding
(free) problem values correspond to rows with \code{alter != 0} and \code{value_in != 0}.
\item \code{prob_con_df}: problem constraints data frame, useful for troubleshooting problems that failed (identify unmet
constraints). It contains one observation (row) for each constraint involved in each balancing problem, with the following
columns:
\itemize{
\item \code{proc_grp} (num): processing group id.
\item \code{con_type} (chr): problem constraint type. Possible values are:
\itemize{
\item \code{"balancing constraint"};
\item \code{"temporal aggregation constraint"};
\item \code{"period value bounds"}.
}

While \emph{balancing constraints} are specicied by the user, the other two types of constraints (\emph{temporal aggregation
constraints} and \emph{period value bounds}) are automatically added to the problem by the function (when applicable).
\item \code{name} (chr): constraint label or time series (variable) name.
\item \code{t} (num): time id (\code{1:nrow(in_ts)}); id of the first period of the temporal group for a \emph{temporal aggregation constraint}.
\item \code{time_val} (num): time value (\code{stats::time(in_ts)}); value of the first period of the temporal group for a \emph{temporal
aggregation constraint}. It conceptually corresponds to \eqn{year + (period - 1) / frequency}.
\item \code{l}, \code{u}, \code{Ax_in}, \code{Ax_out} (num): initial and final constraint elements \eqn{\left( \mathbf{l} \le A \mathbf{x} \le 
  \mathbf{u} \right)}{(l <= Ax <= u)}.
\item \code{discr_in}, \code{discr_out} (num): initial and final constraint discrepancies \eqn{\left( \max \left( 0, \mathbf{l} - A 
  \mathbf{x}, A \mathbf{x} - \mathbf{u} \right) \right)}{(max(0, l - Ax, Ax - u))}.
\item \code{validation_tol} (num): specified tolerance for validation purposes (argument \code{validation_tol}).
\item \code{unmet_flag} (logi): \code{TRUE} if the constraint is not met (\code{discr_out > validation_tol}), \code{FALSE} otherwise.
}

Columns \code{con_type + name + t} and \code{con_type + name + time_val} both constitute a \emph{unique key} (distinct rows) for the
data frame. Constraint bounds \eqn{\mathbf{l = u}}{l = u} for \code{EQ} constraints, \eqn{\mathbf{l} = -\infty}{l = -Inf} for
\code{LE} constraints, \eqn{\mathbf{u} = \infty}{u = Inf} for \code{GE} constraints, and include the tolerances, when applicable,
specified with arguments \code{tolV}, \code{tolV_temporal} and \code{tolP_temporal}.
\item \code{osqp_settings_df}: OSQP settings data frame. It contains one observation (row) for each problem (processing group)
solved with OSQP (\code{proc_grp_df$sol_type = "osqp"}), with the following columns:
\itemize{
\item \code{proc_grp} (num): processing group id.
\item one column corresponding to each element of the list returned by the \code{osqp::GetParams()} method applied
to a \emph{OSQP solver object} (class "OSQP_Model" object as returned by \code{\link[osqp:osqp]{osqp::osqp()}}), e.g.:
\itemize{
\item Maximum iterations (\code{max_iter});
\item Primal and dual infeasibility tolerances (\code{eps_prim_inf} and \code{eps_dual_inf});
\item Solution polishing flag (\code{polishing});
\item Number of scaling iterations (\code{scaling});
\item etc.
}
\item extra settings specific to \code{\link[=tsbalancing]{tsbalancing()}}:
\itemize{
\item \code{prior_scaling} (logi): \code{TRUE} if the problem data were scaled (using the average of the free (nonbinding) problem
values as the scaling factor) prior to solving with OSQP, \code{FALSE} otherwise.
\item \code{require_polished} (logi): \code{TRUE} if a polished solution from OSQP (\ifelse{latex}{\code{osqp_sol_info_df 
    $status_polish = 1}}{\code{osqp_sol_info_df$status_polish = 1}}) was required for this step in order to end the solving
sequence, \code{FALSE} otherwise. See \code{vignette("osqp-settings-sequence-dataframe")} for more details on the solving sequence
used by \code{\link[=tsbalancing]{tsbalancing()}}.
}
}

Column \code{proc_grp} constitutes a \emph{unique key} (distinct rows) for the data frame. Visit
\url{https://osqp.org/docs/interfaces/solver_settings.html} for all available OSQP settings. Problems (processing groups) for
which the initial solution was returned (\code{proc_grp_df$sol_type = "initial"}) are not included in this data frame.
\item \code{osqp_sol_info_df}: OSQP solution information data frame. It contains one observation (row) for each problem
(processing group) solved with OSQP (\code{proc_grp_df$sol_type = "osqp"}), with the following columns:
\itemize{
\item \code{proc_grp} (num): processing group id.
\item one column corresponding to each element of the \code{info} list of a \emph{OSQP solver object} (class "OSQP_Model"
object as returned by \code{\link[osqp:osqp]{osqp::osqp()}}) after having been solved with the \code{osqp::Solve()} method, e.g.:
\itemize{
\item Solution status (\code{status} and \code{status_val});
\item Polishing status (\code{status_polish});
\item Number of iterations (\code{iter});
\item Objective function value (\code{obj_val});
\item Primal and dual residuals (\code{pri_res} and \code{dua_res});
\item Solve time (\code{solve_time});
\item etc.
}
\item extra information specific to \code{\link[=tsbalancing]{tsbalancing()}}:
\itemize{
\item \code{prior_scaling_factor} (num): value of the scaling factor when \ifelse{latex}{\code{osqp_settings_df $prior_scaling 
    = TRUE}}{\code{osqp_settings_df$prior_scaling = TRUE}} (\code{prior_scaling_factor = 1.0} otherwise).
\item \code{obj_val_ori_prob} (num): original balancing problem's objective function value, which is the OSQP objective function
value (\code{obj_val}) on the original scale (when \code{osqp_settings_df$prior_scaling = TRUE}) plus the constant term of
the original balancing problem's objective function, i.e., \ifelse{latex}{\code{obj_val_ori_prob = obj_val * 
    prior _scaling_factor + <constant term>}}{\code{obj_val_ori_prob = obj_val * prior_scaling_factor + <constant term>}},
where \verb{<constant term>} corresponds to
\eqn{\mathbf{y}^{\mathrm{T}} W \mathbf{y}}{y' W y}. See section \strong{Details} for the definition of vector
\eqn{\mathbf{y}}{y}, matrix \eqn{W} and, more generally speaking, the complete expression of the balancing problem's
objective function.
}
}

Column \code{proc_grp} constitutes a \emph{unique key} (distinct rows) for the data frame. Visit \url{https://osqp.org} for more information on
OSQP. Problems (processing groups) for which the initial solution was returned (\code{proc_grp_df$sol_type = "initial"}) are not
included in this data frame.
}

Note that the "data.frame" objects returned by the function can be explicitly coerced to other types of objects with
the appropriate \verb{as*()} function (e.g., \code{tibble::as_tibble()} would coerce any of them to a tibble).
}
\description{
\if{html,text}{(\emph{version française: 
\url{https://StatCan.github.io/gensol-gseries/fr/reference/tsbalancing.html}})}

\emph{Replication of the G-Series 2.0 SAS\eqn{^\circledR}{®} \strong{GSeriesTSBalancing} macro. See the
G-Series 2.0 documentation for details (Statistics Canada 2016).}

This function balances (reconciles) a system of time series according to a set of linear constraints. The balancing
solution is obtained by solving one or several quadratic minimization problems (see section \strong{Details}) with the OSQP
solver (Stellato et al. 2020). Given the feasibility of the balancing problem(s), the resulting time series data respect
the specified constraints for every time period. Linear equality and inequality constraints are allowed. Optionally, the
preservation of temporal totals may also be specified.
}
\details{
This function solves one balancing problem per processing group (see section \strong{Processing groups} for details). Each of these balancing
problems is a quadratic minimization problem of the following form:
\deqn{\displaystyle 
\begin{aligned}
& \underset{\mathbf{x}}{\text{minimize}} 
& & \mathbf{\left( y - x \right)}^{\mathrm{T}} W \mathbf{\left( y - x \right)} \\
& \text{subject to} 
& & \mathbf{l} \le A \mathbf{x} \le \mathbf{u}
\end{aligned}
}{min(x) (y - x)' W (y - x), subject to l <= Ax <= u}
where
\itemize{
\item \eqn{\mathbf{y}}{y} is the vector of the initial problem values, i.e., the initial time series period values and, when applicable,
temporal totals;
\item \eqn{\mathbf{x}}{x} is the final (reconciled) version of vector \eqn{\mathbf{y}}{y};
\item matrix \eqn{W = \mathrm{diag} \left( \mathbf{w} \right)}{W = diag(w)} with vector \eqn{\mathbf{w}}{w} elements \eqn{w_i = \left\{
    \begin{array}{cl}
      0 & \text{if } |c_i y_i| = 0 \\
      \frac{1}{|c_i y_i|} & \text{otherwise}
    \end{array} \right.
    }{w_i = 0 if |c_i y_i| = 0, w_i = 1/|c_i y_i| otherwise},
where \eqn{c_i} is the alterability coefficient of problem value \eqn{y_i} and cases corresponding to \eqn{|c_i y_i| 
    = 0} are fixed problem values (binding period values or temporal totals);
\item matrix \eqn{A} and vectors \eqn{\mathbf{l}}{l} and \eqn{\mathbf{u}}{u} specify the \emph{balancing constraints}, the \emph{implicit
temporal total aggregation constraints} (when applicable), the \emph{period value (upper and lower) bounds} as well as \emph{\eqn{x_i 
= y_i} constraints for fixed \eqn{y_i} values} \eqn{\left( \left| c_i y_i \right| = 0 \right)}{(|c_i y_i| = 0)}.
}

In practice, the objective function of the problem solved by OSQP excludes constant term \eqn{\mathbf{y}^{\mathrm{T}} W 
\mathbf{y}}{y' W y}, therefore corresponding to \eqn{\mathbf{x}^{\mathrm{T}} W \mathbf{x} - 2 \left( \mathbf{w} \mathbf{y} 
\right)^{\mathrm{T}} \mathbf{x}}{x' W x - 2 (w y)' x}, and the fixed \eqn{y_i} values \eqn{\left( \left| c_i y_i \right| = 0 
\right)}{(|c_i y_i| = 0)} are removed from the problem, adjusting the constraints accordingly, i.e.:
\itemize{
\item rows corresponding to the \emph{\eqn{x_i = y_i} constraints for fixed \eqn{y_i} values} are removed from \eqn{A}, \eqn{
\mathbf{l}}{l} and \eqn{\mathbf{u}}{u};
\item columns corresponding to fixed \eqn{y_i} values are removed from \eqn{A} while appropriately adjusting \eqn{
\mathbf{l}}{l} and \eqn{\mathbf{u}}{u}.
}
\subsection{Alterability Coefficients}{

Alterability coefficients are nonnegative numbers that change the relative cost of modifying an initial problem value.
By changing the actual objective function to minimize, they allow the generation of a wide range of solutions. Since they
appear in the denominator of the objective function (matrix \eqn{W}), the larger the alterability coefficient the less costly
it is to modify a problem value (period value or temporal total) and, conversely, the smaller the alterability coefficient
the more costly it becomes. This results in problem values with larger alterability coefficients proportionally changing more
than the ones with smaller alterability coefficients. Alterability coefficients of \eqn{0.0} define fixed (binding) problem
values while alterability coefficients greater than \eqn{0.0} define free (nonbinding) values. The default alterability
coefficients are \eqn{0.0} for temporal totals (argument \code{alter_temporal}) and \eqn{1.0} for period values (arguments
\code{alter_pos}, \code{alter_neg}, \code{alter_mix}). In the common case of aggregation table raking problems, the period values of the
marginal totals (time series with a coefficient of \eqn{-1} in the balancing constraints) are usually binding (specified
with \code{alter_neg = 0}) while the period values of the component series (time series with a coefficient of \eqn{1} in the
balancing constraints) are usually nonbinding (specified with \code{alter_pos > 0}, e.g., \code{alter_pos = 1}). \emph{Almost binding}
problem values (e.g., marginal totals or temporal totals) can be obtained in practice by specifying very small (almost
\eqn{0.0}) alterability coefficients relative to those of the other (nonbinding) problem values.

\strong{Temporal total preservation} refers to the fact that temporal totals, when applicable, are usually kept “as close as
possible” to their initial value. \emph{Pure preservation} is achieved by default with binding temporal totals while the change
is minimized with nonbinding temporal totals (in accordance with the set of alterability coefficients).
}

\subsection{Validation and troubleshooting}{

Successful balancing problems (problems with a valid solution) have \code{sol_status_val > 0} or, equivalently,
\code{n_unmet_con = 0} or \code{max_discr <= validation_tol} in the output \code{proc_grp_df} data frame. Troubleshooting
unsuccessful balancing problems is not necessarily straightforward. Following are some suggestions:
\itemize{
\item Investigate the failed constraints (\code{unmet_flag = TRUE} or, equivalently, \code{discr_out > validation_tol} in the
output \code{prob_con_df} data frame) to make sure that they do not cause an empty solution space (infeasible problem).
\item Change the OSQP solving sequence. E.g., try:
\enumerate{
\item argument \code{full_sequence = TRUE}
\item argument \code{osqp_settings_df = alternate_osqp_sequence}
\item arguments \code{osqp_settings_df = alternate_osqp_sequence} and \code{full_sequence = TRUE}
}

See \code{vignette("osqp-settings-sequence-dataframe")} for more details on this topic.
\item Increase (review) the \code{validation_tol} value. Although this may sound like \emph{cheating}, the default \code{validation_tol}
value (\eqn{1 \times 10^{-3}}) may actually be too small for balancing problems that involve very large values (e.g.,
in billions) or, conversely, too large with very small problem values (e.g, \eqn{< 1.0}). Multiplying the average scale
of the problem data by the \emph{machine tolerance} (\code{.Machine$double.eps}) gives an approximation of the average size of the
discrepancies that \code{\link[=tsbalancing]{tsbalancing()}} should be able to handle (distinguish from \eqn{0}) and should probably constitute an
\strong{absolute lower bound} for argument \code{validation_tol}. In practice, a reasonable \code{validation_tol} value would likely be
\eqn{1 \times 10^3} to \eqn{1 \times 10^6} times larger than this \emph{lower bound}.
\item Address constraints redundancy. Multi-dimensional aggregation table raking problems are over-specified (involve
redundant constraints) when all totals of all dimensions of the \emph{data cube} are binding (fixed) and a constraint is
defined for all of them. Redundancy also occurs for the implicit temporal aggregation constraints in single- or
multi-dimensional aggregation table raking problems with binding (fixed) temporal totals. Over-specification is generally
not an issue for \code{\link[=tsbalancing]{tsbalancing()}} if the input data are not contradictory with regards to the redundant constraints, i.e.,
if there are no inconsistencies (discrepancies) associated to the redundant constraints in the input data or if they are
\emph{negligible} (reasonably small relative to the scale of the problem data). Otherwise, this may lead to unsuccessful
balancing problems with \code{\link[=tsbalancing]{tsbalancing()}}. Possible solutions would then include:
\enumerate{
\item Resolve (or reduce) the discrepancies associated to the redundant constraints in the input data.
\item Select one marginal total in every dimension, but one, of the data cube and remove the corresponding balancing
constraints from the problem. \emph{This cannot be done for the implicit temporal aggregation constraints}.
\item Select one marginal total in every dimension, but one, of the data cube and make them nonbinding (alterability
coefficient of, say, \eqn{1.0}).
\item Do the same as (3) for the temporal totals of one of the inner-cube component series (make them nonbinding).
\item Make all marginal totals of every dimension, but one, of the data cube \emph{amlost binding}, i.e., specify very small
alterability coefficients (say \eqn{1 \times 10^{-6}}) compared to those of the inner-cube component series.
\item Do the same as (5) for the temporal totals of all inner-cube component series (very small alterability
coefficients, e.g., with argument \code{alter_temporal}).
\item Use \code{\link[=tsraking]{tsraking()}} (if applicable), which handles these inconsistencies by using the Moore-Penrose inverse
(uniform distribution among all binding totals).
}

Solutions (2) to (7) above should only be considered if the discrepancies associated to the redundant constraints
in the input data are \emph{reasonably small} as they would be distributed among the omitted or nonbinding totals with
\code{\link[=tsbalancing]{tsbalancing()}} and all binding totals with \code{\link[=tsraking]{tsraking()}}. Otherwise, one should first investigate solution (1) above.
\item Relax the bounds of the problem constraints, e.g.:
\itemize{
\item argument \code{tolV} for the balancing constraints;
\item arguments \code{tolV_temporal} and \code{tolP_temporal} for the implicit temporal aggregation constraints;
\item arguments \code{lower_bound} and \code{upper_bound}.
}
}
}
}
\section{Processing groups}{
The set of periods of a given reconciliation (raking or balancing) problem is called a \emph{processing group}
and either corresponds to:
\itemize{
\item a \strong{single period} with period-by-period processing or, when preserving temporal totals, for the individual
periods of an incomplete temporal group (e.g., an incomplete year)
\item or the \strong{set of periods of a complete temporal group} (e.g., a complete year) when preserving temporal
totals.
}

The total number of processing groups (total number of reconciliation problems) depends on the set of
periods in the input time series object (argument \code{in_ts}) and on the value of arguments
\code{temporal_grp_periodicity} and \code{temporal_grp_start}.

Common scenarios include \code{temporal_grp_periodicity = 1} (default) for period-by period processing without
temporal total preservation and \code{temporal_grp_periodicity = frequency(in_ts)} for the preservation of annual
totals (calendar years by default). Argument \code{temporal_grp_start} allows the specification of other types of
(\emph{non-calendar}) years. E.g., fiscal years starting on April correspond to \code{temporal_grp_start = 4} with monthly
data and \code{temporal_grp_start = 2} with quarterly data. Preserving quarterly totals with monthly data would
correspond to \ifelse{latex}{\code{temporal_grp _periodicity = 3}}{\code{temporal_grp_periodicity = 3}}.

By default, temporal groups covering more than a year (i.e., corresponding to \ifelse{latex}{\code{
temporal_grp _periodicity > frequency(in_ts)}}{\code{temporal_grp_periodicity > frequency(in_ts)}} start on a
year that is a multiple of \ifelse{latex}{\code{ceiling(temporal_grp _periodicity / frequency(in_ts))}}{\code{
ceiling(temporal_grp_periodicity / frequency(in_ts))}}. E.g., biennial groups corresponding to \ifelse{latex}{
\code{temporal_grp_per iodicity = 2 * frequency(in_ts)}}{\code{temporal_grp_periodicity = 2 * frequency(in_ts)}}
start on an \emph{even year} by default. This behaviour can be changed with argument \code{temporal_grp_start}. E.g., the
preservation of biennial totals starting on an \emph{odd year} instead of an \emph{even year} (default) corresponds to
\code{temporal_grp_start = frequency(in_ts) + 1} (along with \code{temporal_grp_periodicity = 2 * frequency(in_ts)}).

See the \code{\link[=gs.build_proc_grps]{gs.build_proc_grps()}} \strong{Examples} for common processing group scenarios.
}

\section{Comparing \code{\link[=tsraking]{tsraking()}} and \code{\link[=tsbalancing]{tsbalancing()}}}{
\itemize{
\item \code{\link[=tsraking]{tsraking()}} is limited to one- and two-dimensional aggregation table raking problems (with temporal
total preservation if required) while \code{\link[=tsbalancing]{tsbalancing()}} handles more general balancing problems (e.g.,
higher dimensional raking problems, nonnegative solutions, general linear equality and inequality
constraints as opposed to aggregation rules only, etc.).
\item \code{\link[=tsraking]{tsraking()}} returns the generalized least squared solution of the Dagum and Cholette regression-based
raking model (Dagum and Cholette 2006) while \code{\link[=tsbalancing]{tsbalancing()}} solves the corresponding quadratic
minimization problem using a numerical solver. In most cases, \emph{convergence to the minimum} is achieved
and the \code{\link[=tsbalancing]{tsbalancing()}} solution matches the (exact) \code{\link[=tsraking]{tsraking()}} least square solution. It may not be
the case, however, if convergence could not be achieved after a reasonable number of iterations.
Having said that, only in very rare occasions will the \code{\link[=tsbalancing]{tsbalancing()}} solution \emph{significantly} differ
from the \code{\link[=tsraking]{tsraking()}} solution.
\item \code{\link[=tsbalancing]{tsbalancing()}} is usually faster than \code{\link[=tsraking]{tsraking()}}, especially for large raking problems, but is
generally more sensitive to the presence of (small) inconsistencies in the input data associated to
the redundant constraints of fully specified (over-specified) raking problems. \code{\link[=tsraking]{tsraking()}} handles
these inconsistencies by using the Moore-Penrose inverse (uniform distribution among all binding totals).
\item \code{\link[=tsbalancing]{tsbalancing()}} accommodates the specification of sparse problems in their reduced form. This is not
true in the case of \code{\link[=tsraking]{tsraking()}} where aggregation rules must always be fully specified since a
\emph{complete data cube} without missing data is expected as input (every single \emph{inner-cube} component
series must contribute to all dimensions of the cube, i.e., to every single \emph{outer-cube} marginal
total series).
\item Both tools handle negative values in the input data differently by default. While the solutions of
raking problems obtained from \code{\link[=tsbalancing]{tsbalancing()}} and \code{\link[=tsraking]{tsraking()}} are identical when all input data
points are positive, they will differ if some data points are negative (unless argument
\code{Vmat_option = 2} is specified with \code{\link[=tsraking]{tsraking()}}).
\item While both \code{\link[=tsbalancing]{tsbalancing()}} and \code{\link[=tsraking]{tsraking()}} allow the preservation of temporal totals, time
management is not incorporated in \code{\link[=tsraking]{tsraking()}}. For example, the construction of the processing groups
(sets of periods of each raking problem) is left to the user with \code{\link[=tsraking]{tsraking()}} and separate calls must
be submitted for each processing group (each raking problem). That's where helper function
\code{\link[=tsraking_driver]{tsraking_driver()}} comes in handy with \code{\link[=tsraking]{tsraking()}}.
\item The marginal totals (\emph{outer-cube} marginal total series) returned by \code{\link[=tsraking]{tsraking()}} are always
recalculated as the sum of the relevant component series (\emph{inner-cube} component series). The output
values of \strong{binding} marginal totals may therefore slightly differ from the input values. The
magnitude of the differences depends on the initial inconsistencies (that end up being uniformly
distributed by the Moore-Penrose inverse). This is not the case for \code{\link[=tsbalancing]{tsbalancing()}} where the values
of \strong{binding} marginal totals always remain perfectly unchanged, potentially resulting in (small)
discrepancies between the sum of the returned component series and the corresponding marginal totals.
\item \code{\link[=tsbalancing]{tsbalancing()}} returns the same set of series as the input time series object while \code{\link[=tsraking]{tsraking()}}
returns the set of series involved in the raking problem plus those specified with argument \code{id}
(which could correspond to a subset of the input series).
}
}

\examples{
###########
# Example 1: In this first example, the objective is to balance a following simple 
#            accounting table (`Profits = Revenues – Expenses`) for 5 quarters 
#            without modifying `Profits` where `Revenues >= 0` and `Expenses >= 0`.

# Problem specifications
my_specs1 <- data.frame(
  type = c(
    "EQ", rep(NA, 3), 
    "alter", NA, 
    "lowerBd", NA, NA
  ),
  col = c(
    NA, "Revenues", "Expenses", "Profits", 
    NA, "Profits", 
    NA, "Revenues", "Expenses"
  ),
  row = c(
    rep("Accounting Rule", 4), 
    rep("Alterability Coefficient", 2), 
    rep("Lower Bound", 3)
  ),
  coef = c(
    NA, 1, -1, -1,
    NA, 0,
    NA, 0, 0
  )
)
my_specs1

# Problem data
my_series1 <- ts(
  matrix(
    c( 15,  10,  10,
        4,   8,  -1,
      250, 250,   5,
        8,  12,   0,
        0,  45, -55
    ),
    ncol = 3,
    byrow = TRUE
  ),
  start = c(2022, 1),
  frequency = 4,
  names = c("Revenues", "Expenses", "Profits")
)

# Reconcile the data
out_balanced1 <- tsbalancing(
  in_ts = my_series1,
  problem_specs_df = my_specs1,
  display_level = 3
)

# Initial data
my_series1

# Reconciled data
out_balanced1$out_ts

# Check for invalid solutions
any(out_balanced1$proc_grp_df$sol_status_val < 0)

# Display the maximum output constraint discrepancies
out_balanced1$proc_grp_df[, c("proc_grp_label", "max_discr")]


# The solution returned by `tsbalancing()` corresponds to equal proportional changes 
# (pro-rating) and is related to the default alterability coefficients of 1. Equal 
# absolute changes could be obtained instead by specifying alterability coefficients 
# equal to the inverse of the initial values. 
#
# Let’s do this for the processing group 2022Q2 (`timeVal = 2022.25`), with the default 
# displayed level of information (`display_level = 1`). 

my_specs1b <- rbind(
  cbind(my_specs1,  data.frame(timeVal = rep(NA_real_, nrow(my_specs1)))),
  data.frame(
    type = rep(NA, 2),
    col = c("Revenues", "Expenses"),
    row = rep("Alterability Coefficient", 2),
    coef = c(0.25, 0.125),
    timeVal = rep(2022.25, 2)
  )
)
my_specs1b

out_balanced1b <- tsbalancing(
  in_ts = my_series1,
  problem_specs_df = my_specs1b
)

# Display the initial 2022Q2 values and both solutions
cbind(
  data.frame(Status = c("initial", "pro-rating", "equal change")),
  rbind(
    as.data.frame(my_series1[2, , drop = FALSE]), 
    as.data.frame(out_balanced1$out_ts[2, , drop = FALSE]),
    as.data.frame(out_balanced1b$out_ts[2, , drop = FALSE])
  ),
  data.frame(
    Accounting_discr = c(
      my_series1[2, 1] - my_series1[2, 2] - my_series1[2, 3],
      out_balanced1$out_ts[2, 1] - out_balanced1$out_ts[2, 2] - 
        out_balanced1$out_ts[2, 3],
      out_balanced1b$out_ts[2, 1] - out_balanced1b$out_ts[2, 2] - 
        out_balanced1b$out_ts[2, 3]
    ),
    RelChg_Rev = c(
      NA, 
      out_balanced1$out_ts[2, 1] / my_series1[2, 1] - 1,
      out_balanced1b$out_ts[2, 1] / my_series1[2, 1] - 1
    ),
    RelChg_Exp = c(
      NA, 
      out_balanced1$out_ts[2, 2] / my_series1[2, 2] - 1,
      out_balanced1b$out_ts[2, 2] / my_series1[2, 2] - 1
    ),
    AbsChg_Rev = c(
      NA, 
      out_balanced1$out_ts[2, 1] - my_series1[2, 1],
      out_balanced1b$out_ts[2, 1] - my_series1[2, 1]
    ),
    AbsChg_Exp = c(
      NA, 
      out_balanced1$out_ts[2, 2] - my_series1[2, 2],
      out_balanced1b$out_ts[2, 2] - my_series1[2, 2]
    )
  )
)


###########
# Example 2: In this second example, consider the simulated data on quarterly 
#            vehicle sales by region (West, Centre and East), along with a national 
#            total for the three regions, and by type of vehicles (cars, trucks and 
#            a total that may include other types of vehicles). The input data correspond 
#            to directly seasonally adjusted data that have been benchmarked to the 
#            annual totals of the corresponding unadjusted time series data as part 
#            of the seasonal adjustment process (e.g., with the FORCE spec in the 
#            X-13ARIMA-SEATS software). 
#
#            The objective is to reconcile the regional sales to the national sales 
#            without modifying the latter while ensuring that the sum of the sales of 
#            cars and trucks do not exceed 95\% of the sales for all types of vehicles 
#            in any quarter. For illustrative purposes, we assume that the sales of 
#            trucks in the Centre region for the 2nd quarter of 2022 cannot be modified.

# Problem specifications
my_specs2 <- data.frame(
  
  type = c(
    "EQ", rep(NA, 4),
    "EQ", rep(NA, 4),
    "EQ", rep(NA, 4),
    "LE", rep(NA, 3),
    "LE", rep(NA, 3),
    "LE", rep(NA, 3),
    "alter", rep(NA, 4)
  ),
  
  col = c(
    NA, "West_AllTypes", "Centre_AllTypes", "East_AllTypes", "National_AllTypes", 
    NA, "West_Cars", "Centre_Cars", "East_Cars", "National_Cars", 
    NA, "West_Trucks", "Centre_Trucks", "East_Trucks", "National_Trucks", 
    NA, "West_Cars", "West_Trucks", "West_AllTypes", 
    NA, "Centre_Cars", "Centre_Trucks", "Centre_AllTypes", 
    NA, "East_Cars", "East_Trucks", "East_AllTypes",
    NA, "National_AllTypes", "National_Cars", "National_Trucks", "Centre_Trucks"
  ),
  
  row = c(
    rep("National Total - All Types", 5),
    rep("National Total - Cars", 5),
    rep("National Total - Trucks", 5),
    rep("West Region Sum", 4),
    rep("Center Region Sum", 4),
    rep("East Region Sum", 4),
    rep("Alterability Coefficient", 5)
  ),
  
  coef = c(
    NA, 1, 1, 1, -1,
    NA, 1, 1, 1, -1,
    NA, 1, 1, 1, -1,
    NA, 1, 1, -.95,
    NA, 1, 1, -.95,
    NA, 1, 1, -.95,
    NA, 0, 0, 0, 0
  ),
  
  time_val = c(rep(NA, 31), 2022.25)
)

# Beginning and end of the specifications data frame
head(my_specs2, n = 10)
tail(my_specs2)

# Problem data
my_series2 <- ts(
  matrix(
    c(
      43, 49, 47, 136, 20, 18, 12, 53, 20, 22, 26, 61,
      40, 45, 42, 114, 16, 16, 19, 44, 21, 26, 21, 59,
      35, 47, 40, 133, 14, 15, 16, 50, 19, 25, 19, 71,
      44, 44, 45, 138, 19, 20, 14, 52, 21, 18, 27, 74,
      46, 48, 55, 135, 16, 15, 19, 51, 27, 25, 28, 54
    ),
    ncol = 12,
    byrow = TRUE
  ),
  start = c(2022, 1),
  frequency = 4,
  names = c(
    "West_AllTypes", "Centre_AllTypes", "East_AllTypes", "National_AllTypes", 
    "West_Cars", "Centre_Cars", "East_Cars", "National_Cars", 
    "West_Trucks", "Centre_Trucks", "East_Trucks", "National_Trucks"
  )
)

# Reconcile without displaying the function header and enforce nonnegative data
out_balanced2 <- tsbalancing(
  in_ts                    = my_series2,
  problem_specs_df         = my_specs2,
  temporal_grp_periodicity = frequency(my_series2),
  lower_bound              = 0,
  quiet                    = TRUE
)

# Initial data
my_series2

# Reconciled data
out_balanced2$out_ts

# Check for invalid solutions
any(out_balanced2$proc_grp_df$sol_status_val < 0)

# Display the maximum output constraint discrepancies
out_balanced2$proc_grp_df[, c("proc_grp_label", "max_discr")]


###########
# Example 3: Reproduce the `tsraking_driver()` 2nd example with `tsbalancing()` 
#            (1-dimensional raking problem with annual total preservation).

# `tsraking()` metadata
my_metadata3 <- data.frame(
  series = c("cars_alb", "cars_sask", "cars_man"),
  total1 = rep("cars_tot", 3)
)
my_metadata3

# `tsbalancing()` problem specifications
my_specs3 <- rkMeta_to_blSpecs(my_metadata3)
my_specs3

# Problem data
my_series3 <- ts(
  matrix(
    c(
      14, 18, 14, 58,
      17, 14, 16, 44,
      14, 19, 18, 58,
      20, 18, 12, 53,
      16, 16, 19, 44,
      14, 15, 16, 50,
      19, 20, 14, 52,
      16, 15, 19, 51
    ),
    ncol = 4,
    byrow = TRUE
  ),
  start = c(2019, 2),
  frequency = 4,
  names = c("cars_alb", "cars_sask", "cars_man", "cars_tot")
)

# Reconcile the data with `tsraking()` (through `tsraking_driver()`)
out_raked3 <- tsraking_driver(
  in_ts = my_series3,
  metadata_df = my_metadata3,
  temporal_grp_periodicity = frequency(my_series3),
  quiet = TRUE
)

# Reconcile the data with `tsbalancing()`
out_balanced3 <- tsbalancing(
  in_ts = my_series3,
  problem_specs_df = my_specs3,
  temporal_grp_periodicity = frequency(my_series3),
  quiet = TRUE
)

# Initial data
my_series3

# Both sets of reconciled data
out_raked3
out_balanced3$out_ts

# Check for invalid `tsbalancing()` solutions
any(out_balanced3$proc_grp_df$sol_status_val < 0)

# Display the maximum output constraint discrepancies from the `tsbalancing()` solutions
out_balanced3$proc_grp_df[, c("proc_grp_label", "max_discr")]

# Confirm that both solutions (`tsraking() and `tsbalancing()`) are the same
all.equal(out_raked3, out_balanced3$out_ts)
}
\references{
Dagum, E. B. and P. Cholette (2006). \strong{Benchmarking, Temporal Distribution and Reconciliation Methods
of Time Series}. Springer-Verlag, New York, Lecture Notes in Statistics, Vol. 186. \doi{10.1007/0-387-35439-5}

Ferland, M., S. Fortier and J. Bérubé (2016). "A Mathematical Optimization Approach to Balancing Time Series:
Statistics Canada’s GSeriesTSBalancing". In \strong{JSM Proceedings, Business and Economic Statistics Section}. Alexandria,
VA: American Statistical Association. 2292-2306.

Ferland, M. (2018). "Time Series Balancing Quadratic Problem — Hessian matrix and vector of linear objective
function coefficients". \strong{Internal document}. Statistics Canada, Ottawa, Canada.

Quenneville, B. and S. Fortier (2012). "Restoring Accounting Constraints in Time Series – Methods and
Software for a Statistical Agency". \strong{Economic Time Series: Modeling and Seasonality}. Chapman & Hall, New York.

SAS Institute Inc. (2015). "The LP Procedure Sparse Data Input Format". \strong{SAS/OR\eqn{^\circledR}{®} 14.1
User's Guide: Mathematical Programming Legacy Procedures}.
\url{https://support.sas.com/documentation/cdl/en/ormplpug/68158/HTML/default/viewer.htm#ormplpug_lp_details03.htm}

Statistics Canada (2016). "The \emph{\strong{GSeriesTSBalancing}} Macro". \strong{G-Series 2.0 User Guide}.
Statistics Canada, Ottawa, Canada.

Statistics Canada (2018). \strong{Theory and Application of Reconciliation (Course code 0437)}.
Statistics Canada, Ottawa, Canada.

Stellato, B., G. Banjac, P. Goulart et al. (2020). "OSQP: an operator splitting solver for quadratic programs".
\strong{Math. Prog. Comp. 12}, 637–672 (2020). \doi{10.1007/s12532-020-00179-2}
}
\seealso{
\code{\link[=tsraking]{tsraking()}} \code{\link[=tsraking_driver]{tsraking_driver()}} \code{\link[=rkMeta_to_blSpecs]{rkMeta_to_blSpecs()}} \code{\link[=gs.build_proc_grps]{gs.build_proc_grps()}} \code{\link[=build_balancing_problem]{build_balancing_problem()}} \link{aliases}
}
