Skip to content

Commit

Permalink
fix calculate derivative
Browse files Browse the repository at this point in the history
  • Loading branch information
dallan-keylogic committed Feb 19, 2024
1 parent 6decc12 commit 3370f4a
Show file tree
Hide file tree
Showing 2 changed files with 222 additions and 20 deletions.
88 changes: 68 additions & 20 deletions idaes/core/solvers/petsc.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from pyomo.core.expr.visitor import identify_variables
import pyomo.dae as pyodae
from pyomo.common import Executable
from pyomo.dae.flatten import flatten_dae_components
from pyomo.dae.flatten import flatten_dae_components, slice_component_along_sets
from pyomo.util.subsystems import (
TemporarySubsystemManager,
create_subsystem_block,
Expand Down Expand Up @@ -428,7 +428,7 @@ def petsc_dae_by_time_element(
symbolic_solver_labels=True,
between=None,
interpolate=True,
calculate_derivatives=True,
calculate_derivatives=False,
previous_trajectory=None,
representative_time=None,
snes_options=None,
Expand Down Expand Up @@ -711,17 +711,17 @@ def petsc_dae_by_time_element(
# May not have trajectory from fixed variables and they
# shouldn't change anyway, so only set not fixed vars
var[t].value = vec[i]
if calculate_derivatives:
# the petsc solver interface does not currently return time
# derivatives, and if it did, they would be estimated based on a
# smaller time step. This option uses Pyomo.DAE's discretization
# equations to calculate the time derivative values
calculate_time_derivatives(m, time)
# return the solver results and trajectory if available
if calculate_derivatives:
# the petsc solver interface does not currently return time
# derivatives, and if it did, they would be estimated based on a
# smaller time step. This option uses Pyomo.DAE's discretization
# equations to calculate the time derivative values
calculate_time_derivatives(m, time, between=between)
# return the solver results and trajectory if available
return PetscDAEResults(results=res_list, trajectory=tj)


def calculate_time_derivatives(m, time):
def calculate_time_derivatives(m, time, between=None):
"""Calculate the derivative values from the discretization equations.
Args:
Expand All @@ -731,19 +731,67 @@ def calculate_time_derivatives(m, time):
Returns:
None
"""
# Leave between an optional argument for backwards compatibility
if between is None:
between=time
for var in m.component_objects(pyo.Var):
if isinstance(var, pyodae.DerivativeVar):
if time in ComponentSet(var.get_continuousset_list()):
parent = var.parent_block()
name = var.local_name + "_disc_eq"
disc_eq = getattr(parent, name)
for i, v in var.items():
try:
if disc_eq[i].active:
v.value = 0 # Make sure there is a value
calculate_variable_from_constraint(v, disc_eq[i])
except KeyError:
pass # discretization equation may not exist at first time
parent_block = var.parent_block()
disc_eq = getattr(parent_block, var.local_name + "_disc_eq")

deriv_dict = dict(
(key, pyo.Reference(slc))
for key, slc in slice_component_along_sets(var, (time,))
)
disc_dict = dict(
(key, pyo.Reference(slc))
for key, slc in slice_component_along_sets(disc_eq, (time,))
)

for key, deriv in deriv_dict.items():
# state = state_dict[key]
disc_eq = disc_dict[key]
for t in time:
if t < between.first() or t > between.last():
# Outside of integration range, skip calculation
continue
# Check time index to decide what constraints to scale
if t == time.first() or t == time.last():
try:
if disc_eq[t].active and not deriv[t].fixed:
old_value = deriv[t].value
deriv[t].value = 0 # Make sure there is a value
calculate_variable_from_constraint(deriv[t], disc_eq[t])
except KeyError:
# Discretization and continuity equations may or may not exist at the first or last time
# points depending on the method. Backwards skips first, forwards skips last, central skips
# both (which means the user needs to provide additional equations)
pass

elif t == between.first() or t == between.last():
# At edges of between, it's unclear which adjacent
# values of state variables have been populated.
# Therefore we might get hit with value errors.

# TODO This calculates the value of the derivative even
# if one of the state var values is from outside the
# integration range, so long as it's initialized. Is
# this the desired behavior?
try:
if disc_eq[t].active and not deriv[t].fixed:
old_value = deriv[t].value
deriv[t].value = 0 # Make sure there is a value
calculate_variable_from_constraint(deriv[t], disc_eq[t])
except ValueError:
# Reset deriv value to old value
if disc_eq[t].active and not deriv[t].fixed:
deriv[t].value = old_value

else:
if disc_eq[t].active and not deriv[t].fixed:
deriv[t].value = 0 # Make sure there is a value
calculate_variable_from_constraint(deriv[t], disc_eq[t])


class PetscTrajectory(object):
Expand Down
154 changes: 154 additions & 0 deletions idaes/core/solvers/tests/test_petsc.py
Original file line number Diff line number Diff line change
Expand Up @@ -988,3 +988,157 @@ def diff_eq_rule(m, t):
petsc.petsc_dae_by_time_element(
m, time=m.time, between=[0.0, 10.0], representative_time=5.0
)

@pytest.mark.unit
@pytest.mark.skipif(not petsc.petsc_available(), reason="PETSc solver not available")
def test_calculate_derivatives():
m = pyo.ConcreteModel()

m.time = pyodae.ContinuousSet(initialize=(0.0, 1.0, 2.0))
m.x = pyo.Var(m.time)
m.u = pyo.Var(m.time)
m.dxdt = pyodae.DerivativeVar(m.x, wrt=m.time)

def diff_eq_rule(m, t):
return m.dxdt[t] == m.x[t] ** 2 - m.u[t]

m.diff_eq = pyo.Constraint(m.time, rule=diff_eq_rule)

discretizer = pyo.TransformationFactory("dae.finite_difference")
discretizer.apply_to(m, nfe=1, scheme="BACKWARD")

m.u.fix(1.0)
m.x[0].fix(0.0)
m.diff_eq[0].deactivate()

res = petsc.petsc_dae_by_time_element(
m,
time=m.time,
between=[0.0, 2.0],
ts_options={
"--ts_type": "beuler",
"--ts_dt": 3e-2,
},
skip_initial=True, # With u and x fixed, no variables to solve for at t0
calculate_derivatives=True
)
# No value assigned at 0 because there's no corresponding discretization equation
assert m.dxdt[0].value is None
# It should backfill values for interpolated points like t=1
assert pyo.value(m.dxdt[1]) == pytest.approx(-0.7580125427537873, rel=1e-3)
assert pyo.value(m.dxdt[2]) == pytest.approx(-0.2034733131369807, rel=1e-3)

@pytest.mark.unit
@pytest.mark.skipif(not petsc.petsc_available(), reason="PETSc solver not available")
def test_calculate_derivatives_integrate_first_half():
m = pyo.ConcreteModel()

m.time = pyodae.ContinuousSet(initialize=(0.0, 1.0, 2.0))
m.x = pyo.Var(m.time)
m.u = pyo.Var(m.time)
m.dxdt = pyodae.DerivativeVar(m.x, wrt=m.time)

def diff_eq_rule(m, t):
return m.dxdt[t] == m.x[t] ** 2 - m.u[t]

m.diff_eq = pyo.Constraint(m.time, rule=diff_eq_rule)

discretizer = pyo.TransformationFactory("dae.finite_difference")
discretizer.apply_to(m, nfe=1, scheme="BACKWARD")

m.u.fix(1.0)
m.x[0].fix(0.0)
m.diff_eq[0].deactivate()

res = petsc.petsc_dae_by_time_element(
m,
time=m.time,
between=[0.0, 1.0],
ts_options={
"--ts_type": "beuler",
"--ts_dt": 3e-2,
},
skip_initial=True, # With u and x fixed, no variables to solve for at t0
calculate_derivatives=True
)
# No value assigned at 0 because there's no corresponding discretization equation
assert m.dxdt[0].value is None
assert pyo.value(m.dxdt[1]) == pytest.approx(-0.7580125427537873, rel=1e-3)
assert m.dxdt[2].value is None

@pytest.mark.unit
@pytest.mark.skipif(not petsc.petsc_available(), reason="PETSc solver not available")
def test_calculate_derivatives_integrate_second_half():
m = pyo.ConcreteModel()

m.time = pyodae.ContinuousSet(initialize=(0.0, 1.0, 2.0))
m.x = pyo.Var(m.time)
m.u = pyo.Var(m.time)
m.dxdt = pyodae.DerivativeVar(m.x, wrt=m.time)

def diff_eq_rule(m, t):
return m.dxdt[t] == m.x[t] ** 2 - m.u[t]

m.diff_eq = pyo.Constraint(m.time, rule=diff_eq_rule)

discretizer = pyo.TransformationFactory("dae.finite_difference")
discretizer.apply_to(m, nfe=1, scheme="BACKWARD")

m.u.fix(1.0)
m.x[1].fix(-0.7580125427537873)
m.diff_eq[0].deactivate()

res = petsc.petsc_dae_by_time_element(
m,
time=m.time,
between=[1.0, 2.0],
ts_options={
"--ts_type": "beuler",
"--ts_dt": 3e-2,
},
skip_initial=True, # With u and x fixed, no variables to solve for at t0
calculate_derivatives=True
)
# No value assigned at 0 because there's no corresponding discretization equation
assert m.dxdt[0].value is None
assert m.dxdt[1].value is None
assert pyo.value(m.dxdt[2]) == pytest.approx(-0.2034733131369807, rel=1e-3)

@pytest.mark.unit
@pytest.mark.skipif(not petsc.petsc_available(), reason="PETSc solver not available")
def test_calculate_derivatives_forward_difference():
m = pyo.ConcreteModel()

m.time = pyodae.ContinuousSet(initialize=(0.0, 1.0, 2.0))
m.x = pyo.Var(m.time)
m.u = pyo.Var(m.time)
m.dxdt = pyodae.DerivativeVar(m.x, wrt=m.time)

def diff_eq_rule(m, t):
return m.dxdt[t] == m.x[t] ** 2 - m.u[t]

m.diff_eq = pyo.Constraint(m.time, rule=diff_eq_rule)

discretizer = pyo.TransformationFactory("dae.finite_difference")
discretizer.apply_to(m, nfe=1, scheme="FORWARD")

m.u.fix(1.0)
m.x[0].fix(0.0)

res = petsc.petsc_dae_by_time_element(
m,
time=m.time,
between=[0.0, 2.0],
ts_options={
"--ts_type": "beuler",
"--ts_dt": 3e-2,
},
skip_initial=True, # With u and x fixed, no variables to solve for at t0
calculate_derivatives=True
)
# It should backfill values for interpolated points like t=1
assert pyo.value(m.dxdt[0]) == pytest.approx(-0.7580125427537873, rel=1e-3)
assert pyo.value(m.dxdt[1]) == pytest.approx(-0.2034733131369807, rel=1e-3)
# No value assigned at 2 because there's no corresponding discretization equation
# TODO I don't understand how a value is getting assigned here
# assert m.dxdt[2].value is None

0 comments on commit 3370f4a

Please sign in to comment.