Skip to content

Commit

Permalink
misc minor improvements in relational constraints, csp_solver, and mo…
Browse files Browse the repository at this point in the history
…m6_Bathy. Also added a tool to detect violations due to combination of multiple reasons
  • Loading branch information
alperaltuntas committed May 21, 2024
1 parent a2b0869 commit 44f44d2
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 28 deletions.
10 changes: 9 additions & 1 deletion ProConPy/csp_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,15 @@ def retrieve_error_msg(self, var, new_value):
)

error_messages = [str(err_msg) for err_msg in s.unsat_core()]
return f'Invalid assignment of {var} to {new_value}. Reason(s): {". ".join(error_messages)}'
msg = f'Invalid assignment of {var} to {new_value}.'
if len(error_messages) == 1:
msg += f' Reason: {error_messages[0]}'
else:
msg +=' Reasons:'
for i, err_msg in enumerate(error_messages):
msg += f' {i+1}: {err_msg}.'
msg = msg.replace('..', '.')
return msg

def register_assignment(self, var, new_value):
"""Register the assignment of the given variable to the given value. The assignment is
Expand Down
26 changes: 8 additions & 18 deletions tests/2_integration/test_constraint_violation.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,29 +164,19 @@ def test_multiple_reasons():

# Reset active stage
Stage.active().reset()

cvars['COMP_ATM'].value = "cam"
cvars['COMP_ROF'].value = "drof"
cvars['COMP_GLC'].value = "cism"

# Combination of three reasons
with pytest.raises(ConstraintViolation) as exc_info:
cvars['COMP_ATM'].value = "cam"
err_msg = str(exc_info.value)
assert "Data land model cannot be coupled with CAM." in err_msg
assert "CLM cannot be coupled with a data runoff model." in err_msg
assert "GLC cannot be coupled with a stub land model." in err_msg
cvars['COMP_WAV'].value = "dwav"

# Combination of six reasons
# Combination of five reasons
with pytest.raises(ConstraintViolation) as exc_info:
cvars['COMP_OCN'].value = "mom"
cvars['COMP_GLC'].value = "cism"
err_msg = str(exc_info.value)
assert "Data land model cannot be coupled with CAM." in err_msg
assert "CLM cannot be coupled with a data runoff model." in err_msg
assert "GLC cannot be coupled with a stub land model." in err_msg
assert "CLM cannot be coupled with a data runoff model" in err_msg
assert "GLC, ROF, and WAV cannot be coupled with SLIM." in err_msg
assert "An active or data atmosphere model is needed to force ocean, ice, and/or runoff models." in err_msg
assert "When MOM|POP is coupled with data atmosphere (datm), LND component must be stub (slnd)." in err_msg

assert "MOM6 cannot be coupled with data wave component" in err_msg
assert "GLC cannot be coupled with a stub land model, unless it is coupled with MOM6" in err_msg
assert "CAM-DLND coupling is not supported" in err_msg

if __name__ == "__main__":
test_constraint_violation_detection()
Expand Down
99 changes: 99 additions & 0 deletions tools/find_combined_reasons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Module to find combined reasons leading to a violation of constraints in visualCaseGen."""

from ProConPy.config_var import ConfigVar, cvars
from ProConPy.stage import Stage
from ProConPy.dev_utils import ConstraintViolation
from ProConPy.csp_solver import csp
from visualCaseGen.cime_interface import CIME_interface
from visualCaseGen.initialize_configvars import initialize_configvars
from visualCaseGen.initialize_widgets import initialize_widgets
from visualCaseGen.initialize_stages import initialize_stages
from visualCaseGen.specs.options import set_options
from visualCaseGen.specs.relational_constraints import get_relational_constraints

import random


def initialize(cime):
"""Initializes visualCaseGen"""
ConfigVar.reboot()
Stage.reboot()
initialize_configvars(cime)
initialize_widgets(cime)
initialize_stages(cime)
set_options(cime)
csp.initialize(cvars, get_relational_constraints(cvars), Stage.first())


def set_preliminary_vars():
"""Sets the preliminary variables to move on to the Components stage"""
cvars["COMPSET_MODE"].value = "Custom"
cvars["INITTIME"].value = "2000"
assert Stage.active().title.startswith("Components")


def valid_options(var):
"""Returns the valid options for a ConfigVar"""
return [opt for opt in var.options if var._options_validities[opt] is True]


def main(ntrial=10, nselect=5, minreason=3):
"""Main function for the script. It generates random component selections and checks for
combined reasons leading to a violation. If a selection leads to a violation of a minimum
number of combined reasons (constraints), the selection histoary and the constraints are printed.
Parameters
----------
ntrial : int
Number of trials to run
nselect : int
Number of selections to make in each trial
minreason : int
Minimum number of reasons leading to a violation to print the error message
"""

cime = CIME_interface()
initialize(cime)
set_preliminary_vars()
comps = [f"COMP_{cc}" for cc in cime.comp_classes]
comps = [
"COMP_ATM",
"COMP_LND",
"COMP_ICE",
"COMP_OCN",
"COMP_ROF",
"COMP_GLC",
"COMP_WAV",
]
comps = ["COMP_ATM", "COMP_LND", "COMP_ICE", "COMP_OCN", "COMP_ROF", "COMP_WAV"]

for i in range(ntrial):
print(f"Trial {i+1}/{ntrial}")
Stage.active().reset()
hist = []
for s in range(nselect):
comp = random.choice(comps)
new_value = random.choice(valid_options(cvars[comp]))
# remove past assignment if exists:
hist = [h for h in hist if h[0] != comp]
hist.append((comp, new_value))
if cvars[comp].value != new_value:
cvars[comp].value = new_value

for comp_other in set(comps) - set([comp]):
var = cvars[comp_other]
for option in var.options:
if var._options_validities[option] is False:
err_msg = csp.retrieve_error_msg(var, option)
nreason = (
err_msg.count(".") - 1
) # number of reasons leading to the violation
if nreason >= minreason:
print("------------------------------------------------")
print(f"Error message for {var.name} = {option}:")
print(err_msg)
print(f"Hist: {hist}")


if __name__ == "__main__":
main()
19 changes: 11 additions & 8 deletions visualCaseGen/specs/relational_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ def get_relational_constraints(cvars):
"If CLM is coupled with DATM, then both ICE and OCN must be stub.",

Implies(COMP_ATM=="satm", And(COMP_ICE=="sice", COMP_ROF=="srof", COMP_OCN=="socn")) :
"An active or data atmosphere model is needed to force ocean, ice, and/or runoff models.",
"An active/data atmosphere is needed to force ocean, ice, and/or runoff models.",

Implies(COMP_LND=="slnd", COMP_GLC=="sglc") :
"GLC cannot be coupled with a stub land model.",
Implies(COMP_LND=="slnd", Or(COMP_OCN=="mom", COMP_GLC=="sglc")) :
"GLC cannot be coupled with a stub land model, unless it is coupled with MOM6.",

Implies(COMP_LND=="slim", And(COMP_GLC=="sglc", COMP_ROF=="srof", COMP_WAV=="swav")) :
"GLC, ROF, and WAV cannot be coupled with SLIM.",
Expand All @@ -66,7 +66,7 @@ def get_relational_constraints(cvars):
"CLM cannot be coupled with a data runoff model.",

Implies(COMP_LND=="dlnd", COMP_ATM!="cam") : # TODO: check this constraint.
"Data land model cannot be coupled with CAM.",
"CAM-DLND coupling is not supported.",

Implies(COMP_OCN=="docn", COMP_OCN_OPTION != "(none)"):
"Must pick a valid DOCN option.",
Expand Down Expand Up @@ -115,8 +115,8 @@ def get_relational_constraints(cvars):
"Core2 forcing can only be used with T62 grid.",

# mom6_bathy-related constraints ------------------
Implies(And(COMP_OCN=="mom", COMP_LND=="slnd", COMP_ICE=="sice"), OCN_LENY<=179.0):
"MOM6 grid cannot reach the poles when coupled with stub land and ice components.",
Implies(And(COMP_OCN=="mom", COMP_LND=="slnd", COMP_ICE=="sice"), OCN_LENY<180.0):
"If LND and ICE are stub, custom MOM6 grid must exclude poles (singularity).",

Implies(And(COMP_OCN != "mom", COMP_LND!="clm"), GRID_MODE=="Standard"):
"Custom grids can only be generated when MOM6 and/or CLM are selected.",
Expand Down Expand Up @@ -148,8 +148,11 @@ def get_relational_constraints(cvars):
Implies(OCN_GRID_EXTENT=="Global", OCN_LENX==360.0):
"Global ocean model domains must have a length of 360 degrees in the x-direction.",

Implies(OCN_GRID_EXTENT=="Global", And(OCN_LENY>0.0, OCN_LENY<=180.0) ):
"OCN grid length in Y direction must be <= 180.0 when OCN grid extent is global.",
And(OCN_LENY>0.0, OCN_LENY<=180.0):
"OCN grid length in Y direction must be <= 180.0.",

And(OCN_LENX>0.0, OCN_LENX<=360.0):
"OCN grid length in X direction must be <= 360.0.",

# Custom lnd grid constraints ------------------
Implies(COMP_LND!="clm", LND_GRID_MODE=="Standard"):
Expand Down

0 comments on commit 44f44d2

Please sign in to comment.