API reference#

Generated from the docstrings, grouped the way a session uses them. The transformations (drto.infinite_horizon, drto.dynamic_optimization, drto.dynamic_simulation, drto.dynamic_to_steady_state, drto.steady_state_simulation, drto.steady_state_optimization, drto.parameterize) are Pyomo TransformationFactory entries, and their options are documented in the user guide. drto.dynamic_optimization, drto.dynamic_simulation, drto.dynamic_to_steady_state, drto.steady_state_simulation, and drto.steady_state_optimization are also functions taking the model statement, documented below.

The registry#

drto.info(m)[source]#

Return m’s drto registry, creating it on first access.

Parameters:

m (Block) – The model (or block) whose registry is requested.

Returns:

The registry. Repeated calls return the same object, and a cloned model has its own independent registry with every stored component reference remapped to the clone.

Return type:

Info

class drto.Info[source]#

The drto registry: declarations by kind plus a transformation log.

Declarations are recorded by the declaration functions and read back by the transformations. Transformations record themselves, with an outcome annotation, in application order. Displaying the object (console repr or Jupyter) renders the drto-aware view of the model.

components(kind)[source]#

Return the declared components of one kind, in declaration order.

Parameters:

kind (str) – A declaration kind.

Returns:

The declared components (empty if none).

Return type:

tuple

declarations(kind=None)[source]#

Return declaration records, all of them or those of one kind.

Parameters:

kind (str, optional) – A declaration kind. When omitted, return a dict of every kind’s record tuple.

Returns:

The record dicts for kind (empty tuple if none), or the full {kind: records} mapping.

Return type:

tuple or dict

has_declaration(kind)[source]#

Return whether any declaration of kind has been recorded.

has_transformation(name)[source]#

Return whether the transformation name has been applied.

record_declaration(kind, component, **metadata)[source]#

Record a declaration of component under kind.

Parameters:
  • kind (str) – The declaration kind, e.g. 'state' or 'control'. By convention the declaration function name.

  • component (Component or ComponentData) – The declared Pyomo component.

  • **metadata – Declaration details worth reading back, e.g. a control’s profile.

record_transformation(name, **outcome)[source]#

Append name to the ordered transformation log.

Parameters:
  • name (str) – The transformation’s registered name, e.g. 'drto.dynamic_to_steady_state'.

  • **outcome – Annotations of what the transformation did (what it freed or fixed, terms it dropped, whether it kept or collapsed the horizon), rendered in the registry view.

property transformations#

The ordered transformation log, a tuple of record dicts.

Declarations#

The control-side surface:

drto.horizon(component)[source]#

Declare the horizon time set, a pyomo.dae ContinuousSet.

The root handle for the moving-horizon machinery. The set is initialized with the sample grid (the sampling instants), and declaring it captures that grid in the registry: the samples define the stage-cost sum (feature 003) and the sampling time. Exactly one per model, declared before the set is discretized. Tags an attached set or wraps a fresh one.

drto.state(*components)[source]#

Declare one or more state Vars.

A state carries a DerivativeVar only in a dynamic model, so no derivative is required here: a steady-state model’s states qualify as written. Tags attached Vars, wraps one fresh Var, or wraps member-subset slices (holdup[:, "Liq", "NaOH"]) as attached References, so an indexed Var’s algebraic member stays undeclared.

drto.dynamics(*components, **kwargs)[source]#

Declare one or more dynamics equality Constraints.

Currently continuous-time: one side of each member is the DerivativeVar of a declared state, taken with respect to the declared time set, bare or multiplied by derivative-free factors (an IDAES ControlVolume1D writes length * accumulation; a variable-volume balance writes V * dc/dt). Requires horizon and state first. Tags attached Constraints, wraps a fresh one, or builds one as a decorator: @drto.dynamics(m, m.t).

drto.control(*components, profile='piecewise_constant')[source]#

Declare one or more manipulated-input Vars and their profile.

The profile (a pyomo-cvp profile) parameterizes the named controls over the declared time set; it applies to the controls in this call, so a control needing a different parameterization is declared separately. Requires horizon first when one is declared; on a model with no horizon (a steady-state model) the control registers without a profile, since there is no time to parameterize over. Requires pyomo-cvp installed. Tags attached Vars or wraps one fresh Var.

drto.disturbance(*components, profile='piecewise_constant')[source]#

Declare one or more process-noise Vars and their profile.

A disturbance is the estimation-side dual of a control: a Var the estimation frees and a simulation fixes at a realization. The profile (a pyomo-cvp profile) parameterizes it over the declared time set, one value per sample, the same piecewise-constant representation a control takes; process noise is a zero-order hold over a sampling interval, not a value per collocation point. How the noise enters the model equations is the user’s, not fixed here. Requires pyomo-cvp installed; on a model with no horizon the disturbance registers without a profile, since there is no time to parameterize over. Tags attached Vars or wraps one fresh Var.

drto.tracking_stage_cost(*args, **kwargs)[source]#

Declare the tracking stage cost, a per-time-point equality.

One side of each member is the scalar running-cost variable; the other defines the cost, covering every declared state and control and containing nothing else (targets and scales are Params). One per model, indexed over the samples minus the final time (the terminal cost owns it). Declare the states and controls first. Tags, wraps, or builds as a decorator: @drto.tracking_stage_cost(m, sorted(m.t)[:-1]).

drto.economic_stage_cost(*args, **kwargs)[source]#

Declare the economic stage cost, a per-time-point equality.

One side of each member is the scalar running-cost variable; the other defines the cost. One per model. Tags, wraps, or builds as a decorator.

drto.move_suppression(*args, **kwargs)[source]#

Declare the move-suppression cost, a per-sample equality.

One side of each member is the scalar move-cost variable; the other penalizes the control moves, referencing declared control members at that sample and the one before, plus Params (the weights, and the previous action the first member is measured against). One per model, indexed over the samples minus the final time; requires a declared horizon, since a steady-state model has no moves. The objective sums it with the stage costs, the steady-state reduction drops it, and the terminal segment keeps it on the finite horizon and out of the tail integrand. Tags, wraps, or builds as a decorator: @drto.move_suppression(m, sorted(m.t)[:-1]).

drto.tracking_terminal_cost(*args, **kwargs)[source]#

Declare the terminal tracking cost, a scalar equality.

One side is the scalar terminal-cost variable; the other defines the cost, covering every declared state and containing nothing else (targets and scales are Params). One per model; declare the states first. Tags, wraps, or builds as a decorator: @drto.tracking_terminal_cost(m).

drto.initial_condition(*components, **kwargs)[source]#

Declare one or more initial-condition equality Constraints.

One side of each is a declared state at the first time point; the other is a mutable Param, which a loop overwrites with each measurement into. Tags attached Constraints, wraps a fresh one, or builds one as a decorator: @drto.initial_condition(m).

drto.terminal_constraint(*args, **kwargs)[source]#

Declare the terminal constraint, referencing only final-time states.

A single Constraint whose variables are all declared states at the final time point, which is what separates it from a path constraint. Tags, wraps, or builds as a decorator: @drto.terminal_constraint(m).

drto.steady_state(owner, target)[source]#

Pair a declared state with the mutable Param holding its setpoint.

The target the tracking costs drive toward, populated by the steady-state/RTO solve, which is why the pairing is recorded: drto writes each solved state value into its target. One pair per call; returns the target, so a fresh Param wraps: m.z_ss = drto.steady_state(m.z, pyo.Param(initialize=0.5, mutable=True)).

drto.steady_state_control(owner, target)[source]#

Pair a declared control with the mutable Param holding its setpoint.

The control target the tracking costs drive toward. One pair per call; returns the target, so a fresh Param wraps.

The estimation-side surface:

drto.measurement(*components)[source]#

Declare one or more measurement Params.

The measured values over the window, mutable Params drto refreshes each step like the initial-condition Params. They appear in the estimation cost residuals; the measurement map h(z) is written inline in the cost, so there is nothing else to tag. Indexed by the declared time set when a horizon is declared. Tags attached Params or wraps one fresh Param.

drto.estimated_parameter(*components)[source]#

Declare one or more Vars for unknown model parameters to estimate.

The parameters are constant over the window, so they are not indexed by the declared time set. Shared with the steady-state data-reconciliation mode, so no horizon is required. Tags attached Vars or wraps one fresh Var.

drto.estimation_stage_cost(*args, **kwargs)[source]#

Declare the estimation stage cost, a per-time-point equality.

One side of each member is the scalar running estimation-cost variable (the measurement residual plus the process-noise penalty); the other defines it. One per model, indexed over the samples minus the final time (the terminal term owns it). Tags, wraps, or builds as a decorator.

drto.estimation_terminal_cost(*args, **kwargs)[source]#

Declare the terminal estimation cost, a scalar equality.

One side is the scalar terminal estimation-cost variable (the current-state measurement residual at the present time, no process noise); the other defines it. One per model. Tags, wraps, or builds as a decorator.

drto.arrival_cost(*args, **kwargs)[source]#

Declare the arrival cost, a scalar equality.

One side is the scalar arrival-cost variable (the soft prior on the window’s initial state); the other defines it. The soft dual of the control-side initial condition. One per model. Tags, wraps, or builds as a decorator.

Objective assembly#

drto.build_objective(m, zero=False)[source]#

Install the model’s objective, exactly one active minimize Objective.

Parameters:
  • m (Block) – The declared model.

  • zero (bool, optional) – The marked case, which installs a constant-zero objective instead of assembling the live cost terms. The simulation transforms pass this, since a simulation has no cost to assemble.

Returns:

The installed objective.

Return type:

Objective

Raises:

ValueError – If no cost term is live and zero was not requested.

From a model statement#

The registered transformations take a model. These take the function that builds one, so a script with a builder reaches its result in one call.

drto.dynamic_optimization(build, N=None, h=None, ncp=3, scheme='LAGRANGE-RADAU', infinite_horizon=False, tracking_weight=None)[source]#

Build a model, discretize it, and assemble the horizon optimization.

Takes the model statement rather than a model, so one call reaches a problem ready to solve. The builder contract is feature 006’s: build returns a declared, undiscretized model, its first two parameters are the interval count and the sampling time named N and h, and every parameter has a default, so the bare build() is legal.

A builder holds its constant inputs by fixing them at the declared sample points, which is all an undiscretized model has, and discretization here completes them. Before dae.collocation runs, the members fixed at every sample point are recorded with their values, and the members it adds to those are fixed afterward at the value of the sample before them. The declared controls and disturbances are left out, since drto.parameterize and the modes set those, and so is anything fixed at only some samples, an initial-condition pin.

Parameters:
  • build (callable) – The model statement. Called with whichever of N and h were given, by keyword, so an omitted one takes the builder’s default.

  • N (int, optional) – Intervals, passed to the builder.

  • h (float, optional) – Sampling time, passed to the builder.

  • ncp (int, optional) – Collocation points per finite element (default 3).

  • scheme (str, optional) – The collocation scheme (default "LAGRANGE-RADAU").

  • infinite_horizon (bool or mapping, optional) – False (the default) adds no terminal segment. True applies drto.infinite_horizon with its own defaults, and a mapping passes its contents as that transformation’s options.

  • tracking_weight (float, optional) – Passed to the registered transformation when given, weighting the tracking stage cost against the economic one.

Returns:

The model the builder returned, discretized and assembled in place. It is uninitialized, so drto.cold_start_dynamic and the other initializers run on it afterward.

Return type:

Block

Raises:

ValueError – If the builder returns a model whose declared time set is already discretized, since this function owns the mesh.

Examples

m = drto.dynamic_optimization(build, N=50, infinite_horizon=True)
drto.dynamic_simulation(build, N=None, h=None, ncp=3, scheme='LAGRANGE-RADAU', controls=None, disturbances=None)[source]#

Build a model, discretize it, and prepare the forward integration.

Takes the model statement rather than a model, so one call reaches a square system ready to solve. The builder contract is feature 006’s: build returns a declared, undiscretized model, its first two parameters are the interval count and the sampling time named N and h, and every parameter has a default, so the bare build() is legal.

A builder holds its constant inputs by fixing them at the declared sample points, which is all an undiscretized model has, and discretization here completes them, the same rule feature 006 states for drto.dynamic_optimization and the same code.

Parameters:
  • build (callable) – The model statement. Called with whichever of N and h were given, by keyword, so an omitted one takes the builder’s default.

  • N (int, optional) – Intervals, passed to the builder.

  • h (float, optional) – Sampling time, passed to the builder.

  • ncp (int, optional) – Collocation points per finite element (default 3).

  • scheme (str, optional) – The collocation scheme (default "LAGRANGE-RADAU").

  • controls (mapping, optional) – Passed to the registered transformation when given, mapping a declared control to the value it is fixed at. The keys are names here, since the build happens inside the call and the caller never holds the components the transformation’s other key form takes.

  • disturbances (mapping, optional) – Passed to the registered transformation when given, mapping a declared disturbance to its realization, keyed by name for the same reason.

Returns:

The model the builder returned, discretized and prepared in place. It is square, so an NLP solver integrates it forward.

Return type:

Block

Raises:

ValueError – If the builder returns a model whose declared time set is already discretized, since this function owns the mesh.

Examples

plant = drto.dynamic_simulation(build, N=1, controls={"u": 0.3})
drto.dynamic_to_steady_state(build)[source]#

Build a model and reduce it to its steady-state form.

Takes the model statement rather than a model, so a script that already has a builder reaches its steady system in one call. The builder contract is feature 006’s: build returns a declared, undiscretized model, its first two parameters are the interval count and the sampling time, and every parameter has a default, so the bare build() this makes is legal.

A result declaring a horizon is reduced through the registered drto.dynamic_to_steady_state transformation. A result with no declared horizon is already steady and is returned untouched, so a statement that constructs its steady form natively (an IDAES flowsheet built with dynamic=False) never builds the dynamic model at all.

Parameters:

build (callable) – The model statement, called with no arguments.

Returns:

The steady-state model. This is the object build returned, since the function owns the model it just built and reduces it in place. A caller holding a model of its own keeps TransformationFactory('drto.dynamic_to_steady_state').create_using for the form that leaves the source unchanged.

Return type:

Block

Examples

ss = drto.dynamic_to_steady_state(build)
drto.steady_state_simulation(build, controls=None, disturbances=None)[source]#

Build a model and reduce it to the fixed-input equilibrium.

Takes the model statement rather than a model, so a script that already has a builder reaches its equilibrium in one call. The builder contract is feature 006’s: build returns a declared, undiscretized model, its first two parameters are the interval count and the sampling time, and every parameter has a default, so the bare build() this makes is legal. The steady modes pass no N and no h, since the reduction collapses the grid either way.

Nothing is discretized on this path. The registered transformation composes drto.dynamic_to_steady_state (feature 005) for a model declaring a horizon and dynamics, and a statement that constructs its steady form natively takes that reduction’s skip.

Parameters:
  • build (callable) – The model statement, called with no arguments.

  • controls (mapping, optional) – Passed to the registered transformation when given, mapping a declared control to the value it is fixed at. The keys are names here, since the build happens inside the call and the caller never holds the components the transformation’s other key form takes.

  • disturbances (mapping, optional) – Passed to the registered transformation when given, mapping a declared disturbance to its standing realization, keyed by name for the same reason.

Returns:

The square equilibrium problem. This is the object build returned, since the function owns the model it just built and transforms it in place. A caller holding a model of its own keeps TransformationFactory('drto.steady_state_simulation').create_using for the form that leaves the source unchanged.

Return type:

Block

Examples

sim = drto.steady_state_simulation(build, controls={"u": 0.3})
drto.steady_state_optimization(build, tracking_weight=None)[source]#

Build a model and assemble the steady-state optimization.

Takes the model statement rather than a model, so a script that already has a builder reaches its RTO problem in one call. The builder contract is feature 006’s: build returns a declared, undiscretized model, its first two parameters are the interval count and the sampling time, and every parameter has a default, so the bare build() this makes is legal. The steady modes pass no N and no h, since the reduction collapses the grid either way.

Nothing is discretized on this path. The registered transformation composes drto.dynamic_to_steady_state (feature 005) for a model declaring a horizon and dynamics, and a statement that constructs its steady form natively takes that reduction’s skip.

Parameters:
  • build (callable) – The model statement, called with no arguments.

  • tracking_weight (float, optional) – Passed to the registered transformation when given, weighting a declared tracking stage cost against the economic one.

Returns:

The RTO problem. This is the object build returned, since the function owns the model it just built and transforms it in place. A caller holding a model of its own keeps TransformationFactory('drto.steady_state_optimization').create_using for the form that leaves the source unchanged.

Return type:

Block

Examples

rto = drto.steady_state_optimization(build)

Diagnostics#

drto.check_index(m, condition_limit=10000000000.0)[source]#

Check whether the declared model is an index-one DAE.

Parameters:
  • m (Block) – A declared, discretized model.

  • condition_limit (float, optional) – The condition estimate above which the numerical layer fails, 1e10 by default.

Returns:

The verdict, the counts, and the named members on failure; prints readably.

Return type:

CheckIndexReport

Raises:

ValueError – On missing declarations, a non-positive condition_limit, or unequal counts of algebraic constraints and variables (the error names the surplus side).

class drto.CheckIndexReport(verdict='', time_point=None, n_algebraic_constraints=0, n_algebraic_variables=0, unmatched_variables=<factory>, unmatched_constraints=<factory>, structural_index=None, condition_estimate=None, condition_limit=None, numerical='', notes=<factory>)[source]#

The check’s findings; prints readably.

Parameters:
  • verdict (str)

  • time_point (float)

  • n_algebraic_constraints (int)

  • n_algebraic_variables (int)

  • unmatched_variables (list)

  • unmatched_constraints (list)

  • structural_index (int)

  • condition_estimate (float)

  • condition_limit (float)

  • numerical (str)

  • notes (list)

Scaling#

drto.scale(m, source='point')[source]#

Fill m’s scaling_factor Suffix from the chosen source.

Parameters:
  • m (Block) – A model holding values, which an initializer leaves behind. The constraint factors are measured at those values in every mode.

  • source (str or mapping) – Where each variable group’s magnitude comes from. "point" reads the values the model holds. "bounds" reads the largest absolute bound of the members carrying two finite bounds, and a group with none keeps factor one. A mapping of units to magnitudes gives every group valued in a mapped dimension that entry’s magnitude, and every other group keeps factor one.

Raises:

ValueError – If no unfixed variable holds a value, since the constraint factors are measured at the point the model is sitting at, or if source is none of the three forms.

drto.scaled_solve(m, source='point', solver='pounce', tee=False, options=None)[source]#

Assign the factors and solve, returning the model’s own units.

source is forwarded to scale, so every call measures fresh and replaces a Suffix already on the model. The factors reach the solver through the Suffix, no second model is built, and every solve goes through Pyomo’s native interface. pounce reads the Suffix under nlp_scaling_method=user-scaling, constraint factors through the NL file’s suffix segments and variable factors as a change of variables inside the solver. ipopt needs no option, since the NL writer consumes the Suffix and scales the problem as it writes the file. Any other solver does not receive the factors: the solve runs unscaled and a warning says so. The solution loads into m in its own units whenever the solver returned one, and a solve with no solution leaves the model untouched.

Parameters:
  • m (Block) – A model holding values.

  • source (str or mapping) – scale’s source of magnitudes.

  • solver (str) – The solver’s name.

  • tee (bool) – Whether to stream the solver’s log.

  • options (mapping, optional) – Solver options, overriding the defaults set here.

Returns:

The native results: termination_condition and solution_status are its structured fields.

Return type:

pyomo.contrib.solver.common.results.Results

Initialization#

drto.initialize_steady_state(m, controls=None, scale=None)[source]#

Initialize m from its steady state. See the module docstring.

Parameters:
  • m (Block) – A declared model: steady-state (initialized in place) or dynamic and discretized (initialized from a reduced clone, broadcast flat).

  • controls (mapping, optional) – Declared control (the component, or its name) to the value the steady solve holds it at. Controls not in the mapping hold the values they already have, drto.steady_state_simulation’s convention.

  • scale (str or mapping, optional) – A feature 023 source forwarded to drto.scale before the pipeline runs. The pipeline runs in the model’s own units, and the suffix stays on the model for the solves that follow. The default writes nothing and leaves a Suffix the caller wrote untouched.

Returns:

The pipeline’s report (steady path), or the wrapper adding the broadcast line (dynamic path). Both print readably.

Return type:

InitializeReport or SteadyStateInitReport

Raises:
  • RuntimeError – If pyomo-pounce is not installed.

  • ValueError – On a missing declaration, a guard violation, an unknown control, a valueless unheld control, or a non-square steady system (the error names the unmatched variables and constraints).

class drto.SteadyStateInitReport(pipeline=None, n_broadcast_vars=0, n_grid_points=0, n_derivatives_zeroed=0)[source]#

The dynamic path’s report: the pipeline’s, plus the broadcast.

Parameters:
  • pipeline (object)

  • n_broadcast_vars (int)

  • n_grid_points (int)

  • n_derivatives_zeroed (int)

pipeline: object = None#

The pyomo_pounce.initialize report from the reduced clone.

drto.cold_start_dynamic(m, profile='linear', time_constant=None, point_solves=True, scale=None)[source]#

Initialize m from its declared initial condition to its declared steady-state targets. See the module docstring.

profile is "linear" (the default, a straight line) or "exponential" (a normalized decay landing exactly on the target at the horizon’s end). time_constant is the decay’s time constant in the horizon’s own units, a third of the horizon when not given. It belongs to the exponential profile only. point_solves is the algebra choice: True (the default) runs the per-point solves, False skips them deliberately, the profiles and targets landing without a solve and the report saying so. scale takes a feature 023 source and forwards it to drto.scale before anything runs. The solves run in the model’s own units, and the suffix stays on the model for the solves that follow. The default writes nothing and leaves a Suffix the caller wrote untouched.

Returns a ColdStartReport. Values only, nothing added or removed, and the fixed flags are untouched.

class drto.ColdStartReport(n_states=0, n_grid_points=0, n_derivatives=0, n_controls=0, n_held=0, shape='a line', segment='(none attached)', point_solves='skipped (pyomo-pounce not installed)', pipeline=None, notes=<factory>)[source]#

What the cold start set, and what the per-point solves did.

Parameters:
drto.warm_start_dynamic(m)[source]#

Shift m’s values one sampling time forward. See the module docstring. Returns a WarmStartReport. Values only, nothing added or removed, fixed variables left alone.

class drto.WarmStartReport(dt=0.0, n_copied=0, n_interpolated=0, n_filled=0, tail='(none attached)', filled_names=None)[source]#

What the shift set: copies, interpolations, and end fills.

Parameters:

The advanced step#

drto.advanced_step_controller(m, gradient=False, **kwargs)[source]#

Return the correction of m’s solution to the measured state.

The perturbation is read from the initial-condition Params’ current values, the difference between the measured state the loop wrote and the predicted state the model was solved at (the solve point is the baseline, so writing the measurement first is the expected pattern). Returns pyomo_pounce.sens_solution(), a map from each variable to its corrected value, clamped to bounds. The model itself is not modified.

With gradient=True it returns pyomo_pounce.sens_jacobian() of the declared controls with respect to those Params instead, as nested ComponentMaps: result[control][param]. Unrecognized keyword arguments pass through to the pounce call, so options pounce grows need no change here.

Requires a pounce solve of m. Without one there is no factorization, and the call raises pounce’s own no-session error instructing to solve with pounce first.

The closed loop#

drto.ideal_nmpc(build, steps, h=None, ncp=3, scheme='LAGRANGE-RADAU', initial_condition=None, dynamic_optimization=None, disturbances=None, seed=None, initialize='cold', solver='pounce', scale=None, warm_start=None, tee=False)[source]#

Run the ideal NMPC loop for steps samples. See the module docstring.

Parameters:
  • build (callable) – The model statement. The loop builds both sides from it, the controller over the declared horizon and the plant over one sampling interval, so the two grids agree by construction. The builder contract is feature 006’s.

  • steps (int) – The loop length, in samples.

  • h (float, optional) – Sampling time, passed to both builder calls. Omitted, the builder’s default.

  • ncp (int, optional) – Collocation points per finite element, for both sides (default 3).

  • scheme (str, optional) – The collocation scheme, for both sides (default "LAGRANGE-RADAU").

  • initial_condition (mapping, optional) – Declared state (the component, or its name) to the value written into its initial-condition Params before the first step: a constant for every pinned member, or one value each. Omitted, the Params’ current values are the first actual state.

  • dynamic_optimization (mapping, optional) – The controller-only options: N, the interval count passed to its builder call, infinite_horizon, and tracking_weight. h, ncp, and scheme belong to the loop, which states the mesh once for both sides, and repeating one here is an error.

  • disturbances (mapping, optional) – Declared disturbance (the component, or its name) to its realization: a sequence gives the per-step values as given, a number the standard deviation of independent zero-mean normal draws. A disturbance with no entry is zero.

  • seed (int, optional) – Seeds the disturbance draws, making them reproducible.

  • initialize (str, mapping, or False) – The first solve’s initialization. "cold" (the default) runs drto.cold_start_dynamic on the controller and the process alike, a mapping passing through as its options. "steady" runs drto.initialize_steady_state on each side after it is discretized and before the transforms, the only point that function accepts. False skips initialization.

  • solver (str) – The solver, by name, for every controller and process solve. Every solver warm starts between steps on the shifted values, and ipopt also receives the warm start recipe.

  • scale (str or mapping, optional) – A drto.scale source, "point", "bounds", or a mapping of units to magnitudes. Given, the factors are written on each side once it is built, so both hold them and every solver solve receives them. The default, None, writes nothing and honors a scaling_factor Suffix the caller wrote.

  • warm_start (mapping, optional) – Solver options for the warm-started solves. Under "ipopt" they lay over the default recipe (warm_start_init_point=yes, mu_init=1e-6, warm_start_bound_push and warm_start_mult_bound_push at 1e-9). Under the pounce names they lay over mu_init=1e-6 alone, and under any other solver the mapping is used as given and the default is the shifted values alone.

  • tee (bool) – True streams every solve’s output as the loop runs and returns it on the history’s logs, one (step, side, text) entry per solve in loop order. Quiet by default.

Returns:

The actual closed-loop trajectory. drto.plot_states and drto.plot_controls draw it.

Return type:

NmpcHistory

Raises:
  • ValueError – On an input that is not callable, a mesh option repeated in dynamic_optimization, an unknown state or disturbance name, or a disturbance sequence shorter than the loop.

  • RuntimeError – If the solver is not available, or a solve fails (the error names the step).

drto.asnmpc(build, steps, h=None, ncp=3, scheme='LAGRANGE-RADAU', initial_condition=None, dynamic_optimization=None, advanced_step=None, disturbances=None, seed=None, initialize='cold', solver='pounce', scale=None, warm_start=None, tee=False)[source]#

Run the advanced-step NMPC loop for steps samples. See the module docstring.

Before the loop, the controller is solved in full at the initial state, and that solution’s first moves are implemented as solved, since there is no background solution yet to correct. Each step k then implements its moves on the process and the predictor, writes its disturbance values into the process, simulates the predictor one sample and solves the controller at the predicted state, simulates the process one sample, records the measured state, and corrects the background solution to it for the next step’s moves. The last step skips the prediction, the background solve, and the correction, since nothing implements their moves, so the loop makes steps controller solves, as the ideal loop does.

Parameters:
  • build (callable) – The model statement. The loop builds the controller over the declared horizon, and the process and the predictor over one sampling interval, all from it. The builder contract is feature 006’s.

  • steps (int) – The loop length, in samples.

  • h – As in drto.ideal_nmpc. The initial condition lands in every side.

  • ncp – As in drto.ideal_nmpc. The initial condition lands in every side.

  • scheme – As in drto.ideal_nmpc. The initial condition lands in every side.

  • initial_condition – As in drto.ideal_nmpc. The initial condition lands in every side.

  • dynamic_optimization – As in drto.ideal_nmpc. The initial condition lands in every side.

  • advanced_step (mapping, optional) – Keyword arguments passed to drto.advanced_step_controller as given, and through it to pyomo_pounce.sens_solution. gradient is an error, since the loop reads the corrected solution for the moves it implements.

  • disturbances – As in drto.ideal_nmpc. The realizations reach the process alone, and initialize and scale apply to every side.

  • seed – As in drto.ideal_nmpc. The realizations reach the process alone, and initialize and scale apply to every side.

  • initialize – As in drto.ideal_nmpc. The realizations reach the process alone, and initialize and scale apply to every side.

  • scale – As in drto.ideal_nmpc. The realizations reach the process alone, and initialize and scale apply to every side.

  • warm_start – As in drto.ideal_nmpc. The realizations reach the process alone, and initialize and scale apply to every side.

  • solver (str) – "pounce" or "pounce_v2", for every solve. The correction is a backsolve on the controller’s pounce factorization, so no other solver can run this loop.

  • tee (bool) – As in drto.ideal_nmpc. The logs hold one (step, side, text) entry per solve in loop order, the side being "controller", "predictor", or "process" and the step the one the solve ran in.

Returns:

The actual closed-loop trajectory. drto.plot_states and drto.plot_controls draw it.

Return type:

NmpcHistory

Raises:
  • ValueError – On a solver other than pounce, an advanced_step that is not a mapping or that sets gradient, or any input drto.ideal_nmpc rejects.

  • RuntimeError – If pyomo-pounce is missing, or a solve fails (the error names the step).

drto.nonideal_nmpc(build, steps, h=None, ncp=3, scheme='LAGRANGE-RADAU', initial_condition=None, dynamic_optimization=None, delay='solver', disturbances=None, seed=None, initialize='cold', solver='pounce', scale=None, warm_start=None, tee=False)[source]#

Run the NMPC loop with the solve time in it, for steps samples.

See the module docstring. Each step solves the controller at the measurement, holds the previous move for the delay, and implements the new move for the rest of the interval. The first step’s previous move is the declared control targets, which the process already holds.

Parameters:
  • build (callable) – The model statement. The loop builds the controller over the declared horizon and the process over one sampling interval, as drto.ideal_nmpc does. The builder contract is feature 006’s.

  • steps (int) – The loop length, in intervals.

  • h – As in drto.ideal_nmpc.

  • ncp – As in drto.ideal_nmpc.

  • scheme – As in drto.ideal_nmpc.

  • initial_condition – As in drto.ideal_nmpc.

  • dynamic_optimization – As in drto.ideal_nmpc.

  • delay (str, number, or sequence) – "solver" (the default) takes each solve’s reported wall time and converts it into the declared time units. A number is that delay at every step, and a sequence one per step, both in the declared units.

  • disturbances – As in drto.ideal_nmpc. The step’s realization holds over both pieces of its interval.

  • seed – As in drto.ideal_nmpc. The step’s realization holds over both pieces of its interval.

  • initialize – As in drto.ideal_nmpc. The step’s realization holds over both pieces of its interval.

  • solver – As in drto.ideal_nmpc. The step’s realization holds over both pieces of its interval.

  • scale – As in drto.ideal_nmpc. The step’s realization holds over both pieces of its interval.

  • warm_start – As in drto.ideal_nmpc. The step’s realization holds over both pieces of its interval.

  • tee – As in drto.ideal_nmpc. The step’s realization holds over both pieces of its interval.

Returns:

The actual closed-loop trajectory, with each step’s delay and the instant each move took effect. drto.plot_states and drto.plot_controls draw it, the moves stepping at those instants.

Return type:

NonidealNmpcHistory

Raises:
  • ValueError – On a delay that is neither "solver", a number, nor one number per step, on a negative delay, on delay="solver" where the solver’s seconds do not convert into the declared time units, or on any input drto.ideal_nmpc rejects. Every one of these raises before the sides are built.

  • RuntimeError – If the solver is not available, or a solve fails (the error names the step).

class drto.NonidealNmpcHistory(times=<factory>, logs=<factory>, states=<factory>, moves=<factory>, realizations=<factory>, state_targets=<factory>, control_targets=<factory>, state_bounds=<factory>, control_bounds=<factory>, delays=<factory>, effect_times=<factory>, clamped=<factory>)[source]#

drto.ideal_nmpc’s history, plus what the delay adds.

delays holds each step’s delay in the declared time units, as measured or as prescribed, and effect_times the instant each move took effect, one entry per step. clamped lists the steps whose delay reached the interval, where the previous move held throughout and the new move took effect at the next boundary.

Parameters:
class drto.NmpcHistory(times=<factory>, logs=<factory>, states=<factory>, moves=<factory>, realizations=<factory>, state_targets=<factory>, control_targets=<factory>, state_bounds=<factory>, control_bounds=<factory>)[source]#

The closed loop’s actual trajectory, under the declared names.

times holds the sample instants, the initial one included. states maps each pinned state member’s label to its actual values at those instants, and moves and realizations map each control and disturbance to its per-step values, one shorter than times. The targets are the declared steady-state pairings’ values, the plots’ setpoint lines. state_bounds and control_bounds map each label to the declared (lo, hi), the plots’ bound lines. Under tee=True, logs holds every solve’s output in loop order as (step, side, text).

Parameters:

Approximate NMPC#

drto.approximate_nmpc_data(m, n=1000, method='sobol', inputs=None, ranges=None, gradients=True, solver='pounce', scale=None, seed=0, path=None)[source]#

Sample the assembled optimization into a labeled dataset.

Parameters:
  • m (Block) – The assembled optimization: the declared model with its transforms applied.

  • n (int) – The number of draws.

  • method (str) – The design filling the box: "sobol", "lhs", or "uniform".

  • inputs (iterable of Params, optional) – Sampled Params beyond the initial-condition Params, each needing a ranges entry.

  • ranges (mapping, optional) – Param (or name) to (lo, hi), overriding a default box or supplying one where no default exists.

  • gradients (bool) – Record the derivative of each first action with respect to each sampled Param, read from the pounce factorization.

  • solver (str) – The labeling solver’s name.

  • scale (str or mapping, optional) – A drto.scale source, "point", "bounds", or a mapping of units to magnitudes. Given, the factors are written once at entry, before the first cold start, and every cold start and solve runs against them: one write serves the whole dataset. A caller choosing "point" passes the model at the point to measure. The default, None, writes nothing and honors a scaling_factor Suffix the caller wrote.

  • seed (int) – The design’s seed.

  • path (str, optional) – When given, the dataset is also written there as JSON.

Returns:

The labeled points, the failures, and the generation record. When the model declares steady_state and steady_state_control, the record includes the targets’ values as x_ss and u_ss, which steady_state_enforced training reads.

Return type:

ApproximateNmpcDataset

class drto.ApproximateNmpcDataset(config, points, failures)[source]#

A labeled dataset: the sampled values, the labels, the failures.

points is a list of dicts, one per labeled draw, with keys x (the sampled values by Param name), u0 (the first control action by control name), V (the objective), and, when gradients were recorded, du0_dx (nested by control then Param name). failures records the draws whose solve did not return optimal, each with its x and the termination condition. config is the generation record: the draw count, the design, the seed, the sampled names, and the boxes.

classmethod load(path)[source]#

Read a dataset save wrote.

save(path)[source]#

Write the dataset as JSON; load reads it back.

drto.approximate_nmpc_train(data, validation=0.2, validation_loss='sobolev', training_loss='sobolev', gamma=1.0, hidden=(100, 100, 100), activation='tanh', steady_state_enforced=True, lr=0.001, schedule='cosine', lr_min=1e-05, clip=1.0, weight_decay=0.0, epochs=50000, fine_tune=0.2, seeds=1, device='auto')[source]#

Fit the control policy to a labeled dataset.

Parameters:
  • data (ApproximateNmpcDataset or str) – The training dataset, or a path to one.

  • validation (ApproximateNmpcDataset, str, or float) – The validation set, a path to one, or a fraction split off data, shuffled under the dataset’s own seed.

  • validation_loss (str) – The metric the kept checkpoint and the seeds winner are chosen by, evaluated on the validation set every 10 epochs. "sobolev", the default, is the value error plus gamma times the gradient term, one definition across both phases. "value" is the value error alone. The choice is independent of training_loss.

  • training_loss (str) – The loss the run minimizes. "sobolev", the default, adds gamma times the squared error of the policy’s Jacobian against the stored derivatives to the value error. "value" fits the values alone.

  • gamma (float) – The gradient term’s weight.

  • hidden (tuple of int) – The hidden layer widths.

  • activation (str) – "tanh", "relu", "silu", or "sigmoid".

  • steady_state_enforced (bool) – Build the policy as the net plus the constant offset that makes it return the recorded steady control at the recorded steady state exactly, whatever the weights. Needs the x_ss and u_ss the dataset records when the model declares steady_state and steady_state_control.

  • lr (float) – The learning rate, the cosine schedule’s starting rate.

  • schedule (str) – "cosine" decays the rate to lr_min over the Sobolev phase; "flat" holds lr.

  • lr_min (float) – The cosine schedule’s final rate.

  • clip (float, optional) – The gradient-norm cap per step; None disables clipping.

  • weight_decay (float) – AdamW’s decoupled L2 penalty on the weights, applied in the Sobolev and fine-tune phases alike; 0 disables it.

  • epochs (int) – The training budget; every run trains all of it.

  • fine_tune (float) – The final fraction of the budget trained on the value error alone, at a flat 1e-4; 0 disables the phase.

  • seeds (int) – Networks trained from distinct initializations. The best by the validation loss is kept.

  • device (str) – "auto" picks cuda when torch sees it; any torch device string passes through.

Returns:

The fitted policy, callable in model units, carrying its training history, its validation error, and, when the validation set was split off by fraction, the points it was split to, as validation_index.

Return type:

ApproximateNMPC

class drto.ApproximateNMPC(meta, state)[source]#

The fitted control policy, callable in the model’s own units.

Call it with a mapping of input names to values and it returns the first control action per control, unscaled. history is the kept run’s per-epoch record, and validation_error its best validation loss. save and load round-trip the policy, history included, through one torch file.

classmethod load(path)[source]#

Read a policy save wrote.

save(path)[source]#

Write the policy, its scaling, and its history to one file.

drto.approximate_nmpc_closed_loop(policy, m, samples=50, x0=None, disturbances=None, solver='pounce', scale=None, compare=False)[source]#

Run the fitted policy closed loop against the declared model.

Each action is clamped to the declared control’s bounds before it is applied, the way an actuator holds an out-of-range command at its limit, and moves records the applied values. The plant carries no state bounds, so a step converges wherever the dynamics land and the report records an excursion outside the controller’s box.

Parameters:
  • policy (ApproximateNMPC) – The fitted policy.

  • m (Block) – The assembled optimization the policy was sampled from. With compare it is solved at each visited state; the plant is a one-sample simulation cloned from it either way.

  • samples (int) – The loop length.

  • x0 (mapping, optional) – Declared state (the component, or its name) to the value written into its initial-condition Params before the first step. Omitted, the Params’ current values are the first state.

  • disturbances (mapping, optional) – Declared disturbance (the component, or its name) to its per-step realization, one value per sample. A disturbance with no entry is zero, and the loop is deterministic.

  • solver (str) – The solver for the plant steps and the compare solves.

  • scale (str or mapping, optional) – A drto.scale source, "point", "bounds", or a mapping of units to magnitudes. Given, the factors are written once at entry, before the plant is cloned, so the plant carries them and the compare solves run against the same ones. A caller choosing "point" passes the model at the point to measure. The default, None, writes nothing and honors a scaling_factor Suffix the caller wrote.

  • compare (bool) – Also solve the horizon problem at each visited state and record the control it takes there, beside the policy’s. A visited state the horizon problem does not solve at records nan and the sample’s time, and the loop continues.

Returns:

The visited trajectory, the applied controls, the per-sample stage costs, and, with compare, the solver’s controls at the same states and the samples it failed at. drto.plot_states and drto.plot_controls draw it.

Return type:

ApproximateNmpcReport

class drto.ApproximateNmpcReport(times=<factory>, logs=<factory>, states=<factory>, moves=<factory>, realizations=<factory>, state_targets=<factory>, control_targets=<factory>, state_bounds=<factory>, control_bounds=<factory>, solver_moves=<factory>, solver_failures=<factory>, stage_costs=<factory>)[source]#

The policy’s closed loop, in the loop-history shape.

Everything drto.NmpcHistory records, plus stage_costs, the stage cost at each visited sample, and, when the comparison ran, solver_moves: the control the horizon solve takes at the same visited states, beside the policy’s applied moves. Where that solve did not return optimal, the entry is nan and the sample’s time is recorded in solver_failures. The summary states the closed-loop cost, the stage costs summed; it is computed here and stored nowhere else.

Parameters:

Plotting#

drto.plot_states(m, states=None, t_max=50, element_boundaries=False)[source]#

Plot declared states, one fixed-size panel each, two columns.

states selects by name, component, or member string like "x1[41,1]". With no selection every declared state is drawn, a multi-index state expanding to one panel per member, up to a cap of _MAX_PANELS panels; past it, select members by name. Setpoint lines come from the steady_state pairings.

element_boundaries=True adds squares at the terminal segment’s element boundaries. The segment collocates on Gauss-Legendre points, which lie strictly inside each element, so a boundary value is the element polynomial extended to its edge and tied to the next element by a continuity equation, not a point the solver placed on the trajectory. A boundary far from its neighbors says the mesh is too coarse there, which is what the squares are for. They stay off by default: on a converged trajectory they are the only points away from the curve, so they set the axis limits and a settled state reads as if it were oscillating. Returns the panel axes.

Handed an drto.NmpcHistory instead of a model, draws its actual state trajectories at the recorded sample instants, the setpoints from the recorded targets; states then selects the recorded labels.

drto.plot_controls(m, controls=None, t_max=50)[source]#

Plot declared controls, one fixed-size panel each, two columns.

controls selects by name or component (all by default; controls are time-only). The finite horizon draws as a staircase: each move holds over its sampling interval, the last one to the end of the horizon (the final sample belongs to the terminal cost, so no move starts there). Setpoint lines come from the steady_state_control pairings. Segment controls have no boundary values, so no squares. Returns the panel axes.

Handed an drto.NmpcHistory instead of a model, draws the implemented moves as the staircase they physically are, each held over its sample; controls then selects the recorded labels. A closed-loop report carrying the solver’s controls draws them on the same panels, dashed.

drto.plot_stage_cost(m, t_max=50)[source]#

Plot the tracking stage cost, one fixed-size panel.

The cost variable is read off the declared stage-cost equality (whichever side of it is the scalar). Finite values sit at the samples minus the final time, where only the terminal cost applies. On the tail the replicated stage-cost Expressions carry the values, drawn open at the interior collocation points. A dotted line marks zero, the tracking cost’s settling value. Returns the panel axes.

drto.plot_history(policy)[source]#

Plot a fitted policy’s training and validation losses, one panel.

Both curves come from policy.history, on a log scale against the epoch each checkpoint was taken at. Returns the panel axes.

drto.plot_parity(policy, data, validation=None)[source]#

Plot a fitted policy’s actions against the solver’s, one panel each.

Each panel draws one control, the label on the horizontal axis and the policy’s action on the vertical, with the line where the two agree. Training points draw as circles and validation points as triangles, and each series carries its coefficient of determination in the legend. The split comes from policy.validation_index, the points the training held out, unless a validation dataset is given, in which case its points are the second series. Without either, one series draws. Returns the panel axes.