# Modeling a Mixture in Traditional Representation When modeling mixtures, we are often faced with a large set of ingredients to choose from. A common way to formalize this type of selection problem is to assign each ingredient its own numerical parameter representing the amount of the ingredient in the mixture. A sum constraint imposed on all parameters then ensures that the total amount of ingredients in the mix is always 100%. In addition, there could be other constraints, for instance, to impose further restrictions on individual subgroups of ingredients. In BayBE's language, we call this the *traditional mixture representation*. In this example, we demonstrate how to create a search space in this representation, using a simple mixture of up to six components, which are divided into three subgroups: solvents, bases and phase agents. ```{admonition} Slot-based Representation :class: seealso For an alternative way to describe mixtures, see our [slot-based representation](/examples/Mixtures/slot_based.md). ``` ## Imports ```python import numpy as np import pandas as pd ``` ```python from baybe.constraints import ContinuousLinearConstraint from baybe.parameters import NumericalContinuousParameter from baybe.recommenders import RandomRecommender from baybe.searchspace import SearchSpace ``` ## Parameter Setup We start by creating lists containing our substance labels according to their subgroups: ```python g1 = ["Solvent1", "Solvent2"] g2 = ["Base1", "Base2"] g3 = ["PhaseAgent1", "PhaseAgent2"] ``` Next, we create continuous parameters describing the substance amounts for each group. Here, the maximum amount for each substance depends on its group, i.e. we allow adding more of a solvent compared to a base or a phase agent: ```python p_g1_amounts = [ NumericalContinuousParameter(name=f"{name}", bounds=(0, 80)) for name in g1 ] p_g2_amounts = [ NumericalContinuousParameter(name=f"{name}", bounds=(0, 20)) for name in g2 ] p_g3_amounts = [ NumericalContinuousParameter(name=f"{name}", bounds=(0, 5)) for name in g3 ] ``` ## Constraints Setup Now, we set up our constraints. We start with the overall mixture constraint, ensuring the total of all ingredients is 100%: ```python c_total_sum = ContinuousLinearConstraint( parameters=g1 + g2 + g3, operator="=", coefficients=(1,) * len(g1 + g2 + g3), rhs=100, ) ``` Additionally, we require bases make up at least 10% of the mixture: ```python c_g2_min = ContinuousLinearConstraint( parameters=g2, operator=">=", coefficients=(1,) * len(g2), rhs=10, ) ``` By contrast, phase agents should make up no more than 5%: ```python c_g3_max = ContinuousLinearConstraint( parameters=g3, operator="<=", coefficients=(1,) * len(g3), rhs=5, ) ``` ## Search Space Creation Having both parameter and constraint definitions at hand, we can create our search space: ```python searchspace = SearchSpace.from_product( parameters=[*p_g1_amounts, *p_g2_amounts, *p_g3_amounts], constraints=[c_total_sum, c_g2_min, c_g3_max], ) ``` ## Verification of Constraints To verify that the constraints imposed above are fulfilled, let us draw some random points from the search space: ```python recommendations = RandomRecommender().recommend(batch_size=10, searchspace=searchspace) print(recommendations) ``` Base1 Base2 PhaseAgent1 PhaseAgent2 Solvent1 Solvent2 0 10.397115 14.569624 2.368623 1.529111 7.996599 63.138928 1 16.111562 6.537185 3.359109 1.030172 2.547495 70.414477 2 11.657611 15.676278 0.050534 0.716351 38.439740 33.459486 3 15.415407 9.287401 0.086135 2.146388 60.939828 12.124841 4 3.469562 16.181582 1.590598 0.945728 16.354443 61.458088 5 17.090813 16.393597 0.326458 2.191519 32.827316 31.170297 6 12.596171 7.468988 0.212867 1.811678 73.971458 3.938839 7 5.435569 19.754664 2.599982 0.625025 24.608850 46.975910 8 5.111314 14.504867 2.258961 1.137259 76.952320 0.035279 9 9.643832 3.500032 0.892306 3.331115 63.960365 18.672349 Computing the respective row sums reveals the expected result: ```python stats = pd.DataFrame( { "Total": recommendations.sum(axis=1), "Total_Bases": recommendations[g2].sum(axis=1), "Total_Phase_Agents": recommendations[g3].sum(axis=1), } ) print(stats) ``` Total Total_Bases Total_Phase_Agents 0 100.0 24.966738 3.897734 1 100.0 22.648747 4.389281 2 100.0 27.333889 0.766885 3 100.0 24.702808 2.232524 4 100.0 19.651144 2.536326 5 100.0 33.484410 2.517977 6 100.0 20.065158 2.024545 7 100.0 25.190233 3.225007 8 100.0 19.616182 3.396220 9 100.0 13.143864 4.223422 ```python assert np.allclose(stats["Total"], 100) assert (stats["Total_Bases"] >= 10).all() assert (stats["Total_Phase_Agents"] <= 5).all() ```