Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion cpmpy/expressions/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
_BV_PREFIX = "BV"
_IV_PREFIX = "IV"
_VAR_ERR = f"Variable names starting with {_IV_PREFIX} or {_BV_PREFIX} are reserved for internal use only, chose a different name"
_VAR_STRICT_NAME_CHECK = True

def BoolVar(shape=1, name=None):
"""
Expand Down Expand Up @@ -790,5 +791,58 @@ def _genname(basename, idxs):
return f"{basename}[{stridxs}]" # "<name>[<idx0>,<idx1>,...]"

def _is_invalid_name(name):
return name.startswith(_IV_PREFIX) or name.startswith(_BV_PREFIX)
"""
Check if a variable name is invalid.

In 'strict' mode, the name is invalid if it starts with {_IV_PREFIX} or {_BV_PREFIX}.
In 'non-strict' mode, the name is invalid if it starts with {_IV_PREFIX} or {_BV_PREFIX}
and the variables' counter is greater than the index, i.e. the name is already in use.

Toggle the strict mode with `_enable_strict_variable_name_check()` and `_disable_strict_variable_name_check()`,
or use the context manager `_ignore_strict_variable_name_check()`.
"""
if name.startswith(_IV_PREFIX):
if _VAR_STRICT_NAME_CHECK:
return True
else:
id = int(name[len(_IV_PREFIX):])
if _IntVarImpl.counter > id:
return True # TODO: better error message
else:
return False

elif name.startswith(_BV_PREFIX):
if _VAR_STRICT_NAME_CHECK:
return True
else:
id = int(name[len(_BV_PREFIX):])
if _BoolVarImpl.counter > id:
return True # TODO: better error message
else:
return False

else:
return False

def _enable_strict_variable_name_check():
global _VAR_STRICT_NAME_CHECK
_VAR_STRICT_NAME_CHECK = True

def _disable_strict_variable_name_check():
global _VAR_STRICT_NAME_CHECK
_VAR_STRICT_NAME_CHECK = False


def _ignore_strict_variable_name_check():
"""
Context manager to temporarily disable strict variable name check.
"""
class IgnoreStrictVariableNameCheck:
def __enter__(self):
_disable_strict_variable_name_check()
def __exit__(self, exc_type, exc_value, traceback):
_enable_strict_variable_name_check()
# _update_variable_counters() # TODO: add automatic support for this later (different PR)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for that you would need access to the model here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, with the current update function you would indeed. So currently the only approach to do it somewhat automatic is to create you model before entering the context, and then call:

with ignore_strict_variable_name_check(model=model):
    ...

The context manager then has the model and can call the update for you on exit.

return False # propagate exceptions

return IgnoreStrictVariableNameCheck()
51 changes: 29 additions & 22 deletions cpmpy/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

Model
"""
from __future__ import annotations
import copy
import warnings
from typing import Optional
Expand All @@ -40,6 +41,33 @@

import pickle


def _update_variable_counters(model: Model):
from cpmpy.transformations.get_variables import get_variables_model # avoid circular import
from cpmpy.expressions.variables import _BoolVarImpl, _IntVarImpl, _BV_PREFIX, _IV_PREFIX # avoid circular import

vs = get_variables_model(model)
bv_counter = 0
iv_counter = 0
for v in vs:
if v.name.startswith(_BV_PREFIX):
try:
bv_counter = max(bv_counter, int(v.name[2:])+1)
except: # When name starts with _BV_PREFIX but is not a valid integer (user created name), ignore
pass
elif v.name.startswith(_IV_PREFIX):
try:
iv_counter = max(iv_counter, int(v.name[2:])+1)
except: # When name starts with _IV_PREFIX but is not a valid integer (user created name), ignore
pass

if (_BoolVarImpl.counter > 0 and bv_counter > 0) or \
(_IntVarImpl.counter > 0 and iv_counter > 0):
warnings.warn(f"Model contains auxiliary {_IV_PREFIX}*/{_BV_PREFIX}* variables with the same name as already created. Only add expressions created AFTER loadig this model to avoid issues with duplicate variables.")
_BoolVarImpl.counter = max(_BoolVarImpl.counter, bv_counter)
_IntVarImpl.counter = max(_IntVarImpl.counter, iv_counter)


class Model(object):
"""
CPMpy Model object, contains the constraint and objective expressions
Expand Down Expand Up @@ -284,28 +312,7 @@ def from_file(fname):
with open(fname, "rb") as f:
m = pickle.load(f)
# bug 158, we should increase the boolvar/intvar counters to avoid duplicate names
from cpmpy.transformations.get_variables import get_variables_model # avoid circular import
from cpmpy.expressions.variables import _BoolVarImpl, _IntVarImpl, _BV_PREFIX, _IV_PREFIX # avoid circular import
vs = get_variables_model(m)
bv_counter = 0
iv_counter = 0
for v in vs:
if v.name.startswith(_BV_PREFIX):
try:
bv_counter = max(bv_counter, int(v.name[2:])+1)
except:
pass
elif v.name.startswith(_IV_PREFIX):
try:
iv_counter = max(iv_counter, int(v.name[2:])+1)
except:
pass

if (_BoolVarImpl.counter > 0 and bv_counter > 0) or \
(_IntVarImpl.counter > 0 and iv_counter > 0):
warnings.warn(f"from_file '{fname}': contains auxiliary {_IV_PREFIX}*/{_BV_PREFIX}* variables with the same name as already created. Only add expressions created AFTER loadig this model to avoid issues with duplicate variables.")
_BoolVarImpl.counter = max(_BoolVarImpl.counter, bv_counter)
_IntVarImpl.counter = max(_IntVarImpl.counter, iv_counter)
_update_variable_counters(m)
return m

def copy(self):
Expand Down
Loading