diff --git a/gpkit/constraints/costed.py b/gpkit/constraints/costed.py index 09f93a3e9..70901a97f 100644 --- a/gpkit/constraints/costed.py +++ b/gpkit/constraints/costed.py @@ -24,10 +24,11 @@ def subinplace(self, subs): self.cost = self.cost.sub(subs) ConstraintSet.subinplace(self, subs) - @property - def varkeys(self): - "return all Varkeys present in this ConstraintSet" - return ConstraintSet._varkeys(self, self.cost.varlocs) + def reset_varkeys(self, init_dict=None): + "Resets varkeys to what is in the cost and constraints" + ConstraintSet.reset_varkeys(self, self.cost.varlocs) + if init_dict is not None: + self.varkeys.update(init_dict) def rootconstr_str(self, excluded=None): "The appearance of a ConstraintSet in addition to its contents" diff --git a/gpkit/constraints/linked.py b/gpkit/constraints/linked.py index 340bc461e..d9b1d9c29 100644 --- a/gpkit/constraints/linked.py +++ b/gpkit/constraints/linked.py @@ -27,11 +27,10 @@ class LinkedConstraintSet(ConstraintSet): """ def __init__(self, constraints, include_only=None, exclude=None): ConstraintSet.__init__(self, constraints) - varkeys = self.varkeys linkable = set() - for varkey in varkeys: + for varkey in self.varkeys: name_without_model = varkey.str_without(["models"]) - if len(varkeys[name_without_model]) > 1: + if len(self.varkeys[name_without_model]) > 1: linkable.add(name_without_model) if include_only: linkable &= set(include_only) @@ -39,7 +38,7 @@ def __init__(self, constraints, include_only=None, exclude=None): linkable -= set(exclude) self.linked, self.reverselinks = {}, {} for name in linkable: - vks = varkeys[name] + vks = self.varkeys[name] sub, subbed_vk = None, None for vk in vks: if vk in self.substitutions: @@ -66,6 +65,10 @@ def __init__(self, constraints, include_only=None, exclude=None): self.reverselinks[newvk] = vks with SignomialsEnabled(): # since we're just substituting varkeys. self.subinplace(self.linked) + for constraint in self: + if hasattr(constraint, "reset_varkeys"): + constraint.reset_varkeys() + self.reset_varkeys() def process_result(self, result): super(LinkedConstraintSet, self).process_result(result) diff --git a/gpkit/constraints/prog_factories.py b/gpkit/constraints/prog_factories.py index 01e89fc1c..2cc599af7 100644 --- a/gpkit/constraints/prog_factories.py +++ b/gpkit/constraints/prog_factories.py @@ -1,10 +1,6 @@ "Scripts for generating, solving and sweeping programs" from time import time import numpy as np -from ..nomials.substitution import parse_subs -from ..solution_array import SolutionArray -from ..keydict import KeyDict -from ..varkey import VarKey try: from ipyparallel import Client @@ -16,6 +12,11 @@ except (ImportError, IOError, AssertionError): POOL = None +from ..nomials.substitution import parse_subs +from ..solution_array import SolutionArray +from ..keydict import KeyDict +from ..varkey import VarKey + def _progify_fctry(program, return_attr=None): "Generates function that returns a program() and optionally an attribute." diff --git a/gpkit/constraints/set.py b/gpkit/constraints/set.py index 83d72cc88..33de27811 100644 --- a/gpkit/constraints/set.py +++ b/gpkit/constraints/set.py @@ -18,6 +18,7 @@ def __init__(self, constraints, substitutions=None, recursesubs=True): constraints = [constraints] list.__init__(self, constraints) subs = substitutions if substitutions else {} + self.unused_variables = None if not isinstance(constraints, ConstraintSet): # constraintsetify everything for i, constraint in enumerate(self): @@ -28,6 +29,7 @@ def __init__(self, constraints, substitutions=None, recursesubs=True): # grab the substitutions dict from the top constraintset subs.update(constraints.substitutions) # pylint: disable=no-member if recursesubs: + self.reset_varkeys() self.substitutions = KeyDict.with_keys(self.varkeys, self._iter_subs(subs)) else: @@ -47,6 +49,10 @@ def __getitem__(self, key): variables.sort(key=_var_sort_key) return variables + def __setitem__(self, key, value): + list.__setitem__(self, key, value) + self.reset_varkeys() + __str__ = _str __repr__ = _repr _repr_latex_ = _repr_latex_ @@ -126,20 +132,27 @@ def subinplace(self, subs): "Substitutes in place." for constraint in self: constraint.subinplace(subs) - - @property - def varkeys(self): - "return all Varkeys present in this ConstraintSet" - return self._varkeys() - - def _varkeys(self, init_dict=None): - "return all Varkeys present in this ConstraintSet" - init_dict = {} if init_dict is None else init_dict - out = KeySet(init_dict) + if self.unused_variables is not None: + unused_vars = [] + for var in self.unused_variables: + if var.key in subs: + unused_vars.append(subs[var.key]) + else: + unused_vars.append(var.key) + self.unused_variables = unused_vars + self.reset_varkeys() + + def reset_varkeys(self, init_dict=None): + "Goes through constraints and collects their varkeys." + varkeys = KeySet() + if init_dict is not None: + varkeys.update(init_dict) for constraint in self: if hasattr(constraint, "varkeys"): - out.update(constraint.varkeys) - return out + varkeys.update(constraint.varkeys) + if self.unused_variables is not None: + varkeys.update(self.unused_variables) + self.varkeys = varkeys def as_posyslt1(self): "Returns list of posynomials which must be kept <= 1" diff --git a/gpkit/keydict.py b/gpkit/keydict.py index aba2fe594..d39af3c1c 100644 --- a/gpkit/keydict.py +++ b/gpkit/keydict.py @@ -149,6 +149,19 @@ class KeySet(KeyDict): "KeySets are KeyDicts without values, serving only to filter and map keys" collapse_arrays = False + def add(self, item): + "Adds an item to the keyset" + self[item] = None + + def update(self, *args, **kwargs): + "Iterates through the dictionary created by args and kwargs" + if len(args) == 1: + for item in args[0]: + self.add(item) + else: + for k, v in dict(*args, **kwargs).items(): + self[k] = v + def __getitem__(self, key): "Gets the keys corresponding to a particular key." key, _ = self.parse_and_index(key) diff --git a/gpkit/tests/t_model.py b/gpkit/tests/t_model.py index d44161c97..3c5b51fa3 100644 --- a/gpkit/tests/t_model.py +++ b/gpkit/tests/t_model.py @@ -418,7 +418,24 @@ def test_cvxopt_kwargs(self): self.assertAlmostEqual(sol["cost"], 12., NDIGS["cvxopt"]) -TESTS = [TestModelSolverSpecific] +class Thing(Model): + "a thing, for model testing" + def __init__(self, n, **kwargs): + a = VectorVariable(n, "a", "g/m") + b = VectorVariable(n, "b", "m") + c = Variable("c", 17/4., "g") + Model.__init__(self, None, [a >= c/b], **kwargs) + + +class TestModelNoSolve(unittest.TestCase): + """model tests that don't require a solver""" + def test_modelname_added(self): + t = Thing(2) + for vk in t.varkeys: + self.assertEqual(vk.models, ["Thing"]) + + +TESTS = [TestModelSolverSpecific, TestModelNoSolve] MULTI_SOLVER_TESTS = [TestGP, TestSP] for testcase in MULTI_SOLVER_TESTS: