Running Controlled Experiments
The simplest way to compare different agent designs is to run tournaments between them as explained in details in previous tutorials. Nevertheless, while developing an agent, it is beneficial to be able to run experiments in which you specify exactly some or all factors that may affect agent performance in order to analyze its behavior.
This tutorial will explain how to achieve this at different levels of control. As expected with more control comes more responsibility (and complexity). For this tutorial we will use the SCMLOneshot game but everthing we discuss applies exactly to all SCML environments. Moreover, we will use builtin agents for illustration but of course the same methods apply to any agents.
The recommended method
The recommended method is to create a Context
that exactly defines
the conditions of your experiment. SCML provides a large number of
contexts for oneshot and std (Search SCML
documentation for Context to
learn more). For example, SupplierContext
will create contexts in
which your agent is always a supplier and so on. You can create new
contexts as well. The context that allows you to control exactly what
happens is the RepeatingContext
which receives some predefined
configurations (may be generated through the generate()
method
described leater or defined explicitly) and just repeats them. This
gives you the finest possible control over the experiment. In the
remaining of this tutorial we focus on using generate()
to create
worlds directly but you can also use the generated configurations in a
repeating context.
Levels of control
There are four levels of control for world generation:
The tournament level: In this case you use
negmas.tournament
or — more conveniently —scml.utils.anac2022_*
functions to run a tournament. You can adjust the parameters used to create worlds in this simulation using almost all the same parameters that are received bySCML2024OneShotWorld.generate()
method (next level of control below). It is harder in this case to control the number of simulations and factory assignment.You can use
OneShotWorld.generate()
orStdWorld.generate()
method to generate a single world for which you can either allow the system to select agent types and their placement or with full control over those. Using this approach (recommended), you can control almost all aspects of the simulations except the exact exogenous contracts and profiles for agents.You can just construct
OneShotWorld
andStdWorld
objects directly which allows you to control everything including every single profile and exogenous contract.You can subclass the world and override some of its private members to modify how the simulation runs. For example, you can override
_make_issues()
to change the ranges of issues for negotiations.
This tutorial will focus on the second approach and will touch upon the third.
Using generate()
:
There are two general ways to use the generate()
method of SCML
worlds for world generation:
You pass
random_agent_types=True
: In this case, you need only passagent_types
and the system will generate random simulations using these types. If you want a specific type to be more represented than others, just repeat it in theagent_types
list. You can also passn_agents_per_process
to control how many agents are present on each production level but you cannot control the exact type of each one of them.You pass
random_agent_types=False
: In this case, you need to passagent_types
andagent_processes
(n_agents_per_process
will be ignored). The former gives the type of each single agent in the simulation and the later gives the corresponding production level. This gives you full control over the type of agent controlling every single factory in the simulation.
We will see examples of both of these approaches in this tutorial. Let’s first import in what we need:
from scml.oneshot.world import SCML2023OneShotWorld
from scml.oneshot.world import is_system_agent
from scml.oneshot.agents import GreedyOneShotAgent, GreedySingleAgreementAgent
Controlling agent allocation to factories
You can control which agents are allocated to which factory by using the
generate()
method of the SCML2020OneShotWorld
class (or any SCML
world class). Let’s say that you want to have \(10\) agents with the
first two of type GreedyOneShotAgent
and the rest of the type
GreedySingleAgreementAgentAgent
, here is a simple way to achieve
that:
types = [GreedyOneShotAgent] * 2 + [GreedySingleAgreementAgent] * 8
world = SCML2023OneShotWorld(
**SCML2023OneShotWorld.generate(
agent_types=types,
agent_processes=[0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
n_processes=2,
n_steps=5,
construct_graphs=True,
random_agent_types=False,
)
)
world.draw(what=["contracts-concluded"])
plt.show()
world.run()
plt.show()

as you can see, the first two agents (“01Gre@0” and “00Gre@0”) are of
type GreedyOneshotAgent
and the rest are of type
GreedySingleAgreementAgent
. That is clear from the naming convention
of putting the first \(3\) letters of the type before the @
sign
in the agent name. You can confirm it explicity by checking types:
[a._obj.__class__.__name__ for a in world.agents.values() if not is_system_agent(a.id)]
['GreedyOneShotAgent',
'GreedyOneShotAgent',
'GreedySingleAgreementAgent',
'GreedySingleAgreementAgent',
'GreedySingleAgreementAgent',
'GreedySingleAgreementAgent',
'GreedySingleAgreementAgent',
'GreedySingleAgreementAgent',
'GreedySingleAgreementAgent',
'GreedySingleAgreementAgent']
What happens if we want to create a world in which the number of agents at every level are different. Let’s first try just extending the approach we used before:
types = [GreedyOneShotAgent] * 2 + [GreedySingleAgreementAgent] * 8
fig, axs = plt.subplots(1, 4)
for ax in axs:
world = SCML2023OneShotWorld(
**SCML2023OneShotWorld.generate(
agent_types=types,
n_agents_per_process=(3, 7),
n_processes=2,
n_steps=10,
construct_graphs=True,
)
)
world.draw(axs=ax, steps=(0, world.n_steps), what=["contracts-concluded"])

We can run the last of these worlds just to be sure something happens!!
world.run()
world.draw(what=["contracts-concluded"], steps=(0, world.n_steps - 1));

As you can see, passing a tuple as n_agents_per_process
did not
help. We generated two world. They were different and neither had the
distribution we wanted. That is because in this case, the generator will
be guaranteed to make a world in which the number of agents in every
level is between 3 and 7 not exactly either of them.
types = [GreedyOneShotAgent] * 2 + [GreedySingleAgreementAgent] * 8
fig, axs = plt.subplots(1, 4)
for ax in axs:
world = SCML2023OneShotWorld(
**SCML2023OneShotWorld.generate(
agent_types=types,
n_agents_per_process=[3, 7],
n_processes=2,
n_steps=5,
construct_graphs=True,
)
)
world.draw(axs=ax, what=["contracts-concluded"])

That works. We can also use it to generate deeper graphs of our choosing:
types = [GreedyOneShotAgent] * 2 + [GreedySingleAgreementAgent] * 8
agents_per_process = [2, 3, 2, 3]
world = SCML2023OneShotWorld(
**SCML2023OneShotWorld.generate(
agent_types=types,
n_agents_per_process=agents_per_process,
n_processes=len(agents_per_process),
n_steps=5,
construct_graphs=True,
random_agent_types=False,
)
)
world.draw(what=["contracts-concluded"])
plt.show()

Exactly what the doctors ordered!
Controlling construction paramteres
We have seen now that you can control the agent types. What about having
agents of the same type but with different prarameters? You will need to
do that for example if you want to compare different options for the
initialization parameters of your agent. Because of a technical
difference between the implementations of SCMLOneshot and standard SCML
game, it is much easier to see what is going on in the SCML2021World
case.
In this case, we can use the agent_params
input to generate()
as
follows:
from scml.scml2020.world import SCML2023World
from scml.scml2020.agents import DecentralizingAgent, BuyCheapSellExpensiveAgent
from negmas import NaiveTitForTatNegotiator
types = [DecentralizingAgent] * 2 + [BuyCheapSellExpensiveAgent] * 4
params = [dict(negotiator_type=NaiveTitForTatNegotiator), dict()] + [dict()] * 4
agents_per_process = [2, 3, 1]
world = SCML2023World(
**SCML2023World.generate(
agent_types=types,
agent_params=params,
n_agents_per_process=agents_per_process,
n_processes=len(agents_per_process),
n_steps=5,
construct_graphs=True,
random_agent_types=False,
)
)
world.draw(what=["contracts-concluded"])
plt.show()

By just looking at the graph, we cannot be sure about what happened. Nevertheless, we can still check the construction parameters from the world itself:
print(world.agent_params[:-2])
[{}, {}, {}, {}, {}, {}]
We can see that the first agent had the negotiator-type we asked for and the rest are just getting their default initialization paramters.
For SCMLOneshot agents, the approach is slightly different due to the
fact that the OneShotAgent
is actually a Controller
not an
Agent
in NegMAS’s parallance. The exact meaning of this is not
relevant for our current discussion though. What we care about is
creating agents with controlled construction paramters. Let’s try the
same method:
types = [GreedyOneShotAgent] * 2 + [GreedySingleAgreementAgent] * 4
params = [
dict(controller_params=dict(concession_exponent=0.4)),
dict(controller_params=dict(concession_exponent=3.0)),
] + [dict()] * 4
world = SCML2023OneShotWorld(
**SCML2023OneShotWorld.generate(
agent_types=types,
agent_params=params,
agent_processes=[0, 0, 1, 1, 1, 2],
n_processes=3,
n_steps=5,
construct_graphs=True,
random_agent_types=False,
)
)
world.draw(what=["contracts-concluded"])
plt.show()

Firstly, note that, in this case, we needed to encolose our paramters
dict within another dict and pass it to the key controller_params
.
That is necessary as these paramters are not to be passed to the
adapther used to run the agent within SCMLOneshot but to our agent which
is the controller.
How can we check that it worked? Let’s first try doing the same thing we
did before and examing agent_params
of the world
:
print(world.agent_params[:-2])
[{}, {}, {}, {}, {}, {}]
No … definitely not. The reason is that these are the paramters of the adapter not our controller. To confirm that the concession rate was passed correctly to our agents, we need to check them directly as follows:
for a in list(world.agents.values())[:2]:
print(a._obj._e)
0.4
3.0
Yes. That is what we expected. The first two agents have the concession exponents we passed to them.
Controlling other aspects of the simulation
You can control other aspects of the simulation by passing specific paramters to the generate() method or the World constructor directly.
Here is an example in which we use generate()
and fix the inital
balance of all agents to \(1000\) while fixing the production cost
of everyone to \(20\), increasing the number of production lines to
\(20\), and setting the number of simulation steps (days) to
\(40\) while making all negotiations go for \(100\) steps
instead of \(20\) keeping the number of negotiation steps per day at
\(101\) which means that negotiations are still guaranteed to finish
within the same day in which they are started. This configuration is
very different than the one used by default in the official competition
but you can decide to test it:
types = [GreedyOneShotAgent] * 7
agents_per_process = [2, 3, 2]
world = SCML2023OneShotWorld(
**SCML2023OneShotWorld.generate(
agent_types=types,
n_agents_per_process=agents_per_process,
n_processes=len(agents_per_process),
n_steps=20,
neg_n_steps=100,
production_costs=50,
cost_increases_with_level=False,
initial_balance=1000,
construct_graphs=True,
random_agent_types=False,
)
)
world.draw(what=["contracts-concluded"])
plt.show()

It is easy enough to check that some of these paramters are correct. For example:
world.neg_n_steps
100
As expected. Checking the initial balances and production costs is harder. Let’s look at the initial balances:
for a in world.agents.values():
if is_system_agent(a.id):
continue
print(f"{a.id} -> {a.awi.current_balance}")
00Gr@0 -> 1000
01Gr@0 -> 1000
02Gr@1 -> 1000
03Gr@1 -> 1000
04Gr@1 -> 1000
05Gr@2 -> 1000
06Gr@2 -> 1000
As expected again. What about production cost?
for a in world.agents.values():
if is_system_agent(a.id):
continue
print(f"{a.id} -> {a.awi.profile.cost}")
00Gr@0 -> 50
01Gr@0 -> 50
02Gr@1 -> 50
03Gr@1 -> 50
04Gr@1 -> 50
05Gr@2 -> 50
06Gr@2 -> 50
This time, we will run this world to just see that it still works after all of this mingling:
world.run()
world.draw(what=["contracts-concluded"], steps=(0, world.n_steps))
plt.show()

Seems fine.
Controlling Profiles
In the previous example, we used generae()
to do our bidding instead
of directly calling the world constructore. Why? The main reason is that
generate()
creates profiles and exogenous contracts compatible with
our settings so that it is possible — in principly — to make money in
the generated world. Moreover, this is controllable by its parameters
(see profit_*
parameters
here).
We can push things a little further by controlling the profile of each
agent independently (which in this case is just its production cost). We
will generate a world in which agents have costs from \(1\) to
\(7\).
types = [GreedyOneShotAgent] * 7
agents_per_process = [2, 3, 2]
world = SCML2023OneShotWorld(
**SCML2023OneShotWorld.generate(
agent_types=types,
n_agents_per_process=agents_per_process,
n_processes=len(agents_per_process),
production_costs=list(range(1, 8)),
cost_increases_with_level=False,
construct_graphs=True,
random_agent_types=False,
)
)
world.draw(what=["contracts-concluded"])
plt.show()

Let’s now check the production costs:
for a in world.agents.values():
if is_system_agent(a.id):
continue
print(f"{a.id} -> {a.awi.profile.cost}")
00Gr@0 -> 1
01Gr@0 -> 2
02Gr@1 -> 3
03Gr@1 -> 4
04Gr@1 -> 5
05Gr@2 -> 6
06Gr@2 -> 7
It is crucial here that we passed cost_increases_with_level=False
,
otherwise, the system will just increase the costs of agents in the
second and third production levels.
The disadvantage of this approach is that you cannot control exactly
the exogenous contracts. These are generated by the generate()
method for us. To control this final piece of the world, we need to
directly call the world constructor. We will see now how to do that for
both types of SCML worlds.
Controlling exogenous contracts
Here we cannot use the generate()
method and must call the world
constructor directly. This is the most complex approach as we need to
set everything up exactly right.
Standard SCML2023World
Let’s try to do it for the SCML2023World
first:
import numpy as np
from scml.scml2020.common import FactoryProfile
from scml.scml2020.common import INFINITE_COST, ExogenousContract
types = [DecentralizingAgent] * 3
agents_per_process = [2, 1]
n_processes = len(agents_per_process)
n_lines = 10
# setup the factory profiles. For each factory we
# set production cost to INFINITE_COST for all processes
# except the one it can actually run
profiles = [
FactoryProfile(np.asarray([[3, INFINITE_COST]] * n_lines)),
FactoryProfile(np.asarray([[20, INFINITE_COST]] * n_lines)),
FactoryProfile(np.asarray([[INFINITE_COST, 5]] * n_lines)),
]
# create exogenous contracts
exogenous = [
## exogenous supply
ExogenousContract(
product=0,
quantity=10,
unit_price=5,
time=1,
revelation_time=1,
seller=-1,
buyer=0,
),
ExogenousContract(
product=0,
quantity=10,
unit_price=7,
time=2,
revelation_time=0,
seller=-1,
buyer=0,
),
## exogenous sales
ExogenousContract(
product=0,
quantity=10,
unit_price=5,
time=1,
revelation_time=0,
seller=2,
buyer=-1,
),
]
world = SCML2023World(
process_inputs=np.ones(n_processes),
process_outputs=np.ones(n_processes),
catalog_prices=[10, 20, 30],
profiles=profiles,
agent_types=types,
agent_params=[dict()] * 3,
exogenous_contracts=exogenous,
n_steps=5,
construct_graphs=True,
agent_name_reveals_position=True,
agent_name_reveals_type=True,
)
world.draw(what=["contracts-concluded"])
plt.show()

Let’s check the exogenous contracts in the system then explain what just happened:
from pprint import pprint
pprint(
list(
(
list(str(_) for _ in contracts)
for s, contracts in world.exogenous_contracts.items()
)
)
)
[["Contract(agreement={'time': 1, 'quantity': 10, 'unit_price': 5}, "
"partners=('00De@0', 'SELLER'), annotation={'seller': 'SELLER', 'buyer': "
"'00De@0', 'caller': 'SELLER', 'is_buy': False, 'product': 0}, issues=(), "
'signed_at=-1, executed_at=-1, concluded_at=-1, nullified_at=-1, '
'to_be_signed_at=1, signatures={}, mechanism_state=None, mechanism_id=None, '
"id='fe0624eb-8cfa-468f-affb-4bdcd88f451f')"],
["Contract(agreement={'time': 2, 'quantity': 10, 'unit_price': 7}, "
"partners=('00De@0', 'SELLER'), annotation={'seller': 'SELLER', 'buyer': "
"'00De@0', 'caller': 'SELLER', 'is_buy': True, 'product': 0}, issues=(), "
'signed_at=-1, executed_at=-1, concluded_at=-1, nullified_at=-1, '
'to_be_signed_at=0, signatures={}, mechanism_state=None, mechanism_id=None, '
"id='50763d3c-b112-4b57-b1d9-b29513fb7444')",
"Contract(agreement={'time': 1, 'quantity': 10, 'unit_price': 5}, "
"partners=('BUYER', '02De@1'), annotation={'seller': '02De@1', 'buyer': "
"'BUYER', 'caller': 'BUYER', 'is_buy': True, 'product': 0}, issues=(), "
'signed_at=-1, executed_at=-1, concluded_at=-1, nullified_at=-1, '
'to_be_signed_at=0, signatures={}, mechanism_state=None, mechanism_id=None, '
"id='33a6bda4-b143-4d91-94c5-7de5f9749c39')"],
[],
[],
[]]
You can confirm for yourself that this is exactly what we expected.
Let’s first discuss the profile. In SCML2021World
, an agent’s
profile consists of the production cost per line per product. You
can see the full definition
here.
That is why we needed to create a 2D array of costs.
Exogenous contract structure is self explanatory. You have to specify the product, delivery time, quantity, and unit price. Moreover, you have to specify the time at which this contract is revealed to its agent (which must be before or at the delivery time step). The one thing you should be careful about is setting the buyer to \(-1\) for exogenous sales and the seller to \(-1\) for exogenous supplies. You can in principle have exogenous contracts in the middle of the chain but we do not do that usually.
Let’s try to run this world
world.run()
_, axs = plt.subplots(2)
world.draw(
what=["negotiations-started", "contracts-concluded"],
steps=(0, world.n_steps),
together=False,
axs=axs,
)
plt.show()
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File ~/code/projects/negmas/negmas/situated/world.py:1557, in World._step_a_mechanism(self, mechanism, force_immediate_signing, action)
1556 try:
-> 1557 result = mechanism.step(action)
1558 except Exception as e:
File ~/code/projects/negmas/negmas/mechanisms.py:1114, in Mechanism.step(self, action)
1113 strt = time.perf_counter()
-> 1114 a._on_negotiation_start(state=state)
1115 self._negotiator_times[a.id] += time.perf_counter() - strt
File ~/code/projects/negmas/negmas/negotiators/negotiator.py:301, in Negotiator._on_negotiation_start(self, state)
300 super().set_preferences(self._preferences, force=True)
--> 301 self.on_negotiation_start(state=state)
File ~/code/projects/negmas/negmas/negotiators/controller.py:411, in Controller.on_negotiation_start(self, negotiator_id, state)
410 if negotiator is None:
--> 411 raise ValueError(f"Unknown negotiator {negotiator_id}")
412 return self.call(negotiator, "on_negotiation_start", state=state)
ValueError: Unknown negotiator 00De@0
During handling of the above exception, another exception occurred:
ValueError Traceback (most recent call last)
Cell In[23], line 1
----> 1 world.run()
3 _, axs = plt.subplots(2)
4 world.draw(
5 what=["negotiations-started", "contracts-concluded"],
6 steps=(0, world.n_steps),
7 together=False,
8 axs=axs,
9 )
File ~/code/projects/negmas/negmas/situated/world.py:2280, in World.run(self)
2278 if self.time >= self.time_limit:
2279 break
-> 2280 if not self.step():
2281 break
File ~/code/projects/negmas/negmas/situated/world.py:1880, in World.step(self, n_neg_steps, n_mechanisms, actions, neg_actions)
1878 assert self.__next_operation_index != 0
1879 while self.__next_operation_index != 0:
-> 1880 if not _negotiate(n_neg_steps):
1881 pass
1882 # print(
1883 # "Some negotiations are still running but all should be completed by now"
1884 # )
File ~/code/projects/negmas/negmas/situated/world.py:1800, in World.step.<locals>._negotiate(n_steps_to_run)
1791 if n_mechanisms is not None and len(mechanisms) > n_mechanisms:
1792 mechanisms = mechanisms[:n_mechanisms]
1793 (
1794 _,
1795 _,
1796 n_steps_broken_,
1797 n_steps_success_,
1798 n_broken_,
1799 n_success_,
-> 1800 ) = self._step_negotiations(
1801 [_[0] for _ in mechanisms],
1802 n_steps_to_run,
1803 False,
1804 [_[1] for _ in mechanisms],
1805 action=neg_actions,
1806 )
1807 self.__stepped_mechanisms = self.__stepped_mechanisms.union(
1808 {_[0].id for _ in mechanisms}
1809 )
1810 running = [
1811 _.mechanism.id
1812 for _ in self._negotiations.values()
(...)
1815 and not _.mechanism.state.ended
1816 ]
File ~/code/projects/negmas/negmas/situated/world.py:1648, in World._step_negotiations(self, mechanisms, n_steps, force_immediate_signing, partners, action)
1646 break
1647 mechanism = mechanisms[i]
-> 1648 contract, r = self._step_a_mechanism(
1649 mechanism,
1650 force_immediate_signing,
1651 action=action.get(mechanism.id, None) if action else None,
1652 )
1653 contracts[i] = contract
1654 running[i] = r
File ~/code/projects/negmas/negmas/situated/world.py:1559, in World._step_a_mechanism(self, mechanism, force_immediate_signing, action)
1557 result = mechanism.step(action)
1558 except Exception as e:
-> 1559 result = mechanism.abort()
1560 if not self.ignore_negotiation_exceptions:
1561 raise e
File ~/code/projects/negmas/negmas/mechanisms.py:1228, in Mechanism.abort(self)
1222 """Aborts the negotiation."""
1223 (
1224 self._current_state.has_error,
1225 self._current_state.error_details,
1226 self._current_state.waiting,
1227 ) = (True, "Uncaught Exception", False)
-> 1228 self.on_mechanism_error()
1229 (
1230 self._current_state.broken,
1231 self._current_state.timedout,
1232 self._current_state.agreement,
1233 ) = (True, False, None)
1234 state = self.state
File ~/code/projects/negmas/negmas/mechanisms.py:945, in Mechanism.on_mechanism_error(self)
943 for a in self.negotiators:
944 strt = time.perf_counter()
--> 945 a.on_mechanism_error(state=state)
946 self._negotiator_times[a.id] += time.perf_counter() - strt
File ~/code/projects/negmas/negmas/negotiators/controller.py:474, in Controller.on_mechanism_error(self, negotiator_id, state)
472 negotiator, cntxt = self._negotiators.get(negotiator_id, (None, None))
473 if negotiator is None:
--> 474 raise ValueError(f"Unknown negotiator {negotiator_id}")
475 return self.call(negotiator, "on_mechanism_error", state=state)
ValueError: Unknown negotiator 00De@0
We can see that there were \(2\) concluded exogenous supply contracts and \(1\) concluded exogenous sale contracts. We can also see that there were \(7\) negotiations in total in this world none of them leading to contracts.
SCMLOneshot World
The situation is slightly different for the SCMLOneshot world just because the format of the profile and exogenous contract data structures is slightly different. Here is an example case:
import numpy as np
from scml.oneshot import OneShotProfile
from scml.oneshot import OneShotExogenousContract
from scml.oneshot import DefaultOneShotAdapter
types = [DefaultOneShotAdapter] * 3
params = [dict(controller_type=GreedyOneShotAgent)] * 3
agents_per_process = [2, 1]
n_processes = len(agents_per_process)
n_lines = 10
common = dict(
n_lines=10,
shortfall_penalty_mean=0.2,
disposal_cost_mean=0.1,
shortfall_penalty_dev=0.01,
disposal_cost_dev=0.01,
storage_cost_mean=0.0,
storage_cost_dev=0.0,
)
# setup the factory profiles. For each factory we
profiles = [
OneShotProfile(cost=3, input_product=0, **common),
OneShotProfile(cost=10, input_product=0, **common),
OneShotProfile(cost=7, input_product=1, **common),
]
# create exogenous contracts
exogenous = [
## exogenous supply
OneShotExogenousContract(
product=0,
quantity=10,
unit_price=5,
time=1,
revelation_time=1,
seller=-1,
buyer=0,
),
OneShotExogenousContract(
product=0,
quantity=10,
unit_price=7,
time=2,
revelation_time=0,
seller=-1,
buyer=0,
),
## exogenous sales
OneShotExogenousContract(
product=0,
quantity=10,
unit_price=5,
time=1,
revelation_time=0,
seller=2,
buyer=-1,
),
]
world = SCML2023OneShotWorld(
catalog_prices=[10, 20, 30],
profiles=profiles,
agent_types=types,
agent_params=params,
exogenous_contracts=exogenous,
n_steps=5,
construct_graphs=True,
agent_name_reveals_position=True,
agent_name_reveals_type=True,
)
world.draw(what=["contracts-concluded"])
plt.show()
The world is constructed. Lets run it and see what happens:
world.run()
_, axs = plt.subplots(2)
world.draw(
what=["negotiations-started", "contracts-concluded"],
steps=(0, world.n_steps),
together=False,
axs=axs,
)
plt.show()
You can confirm for yourself that this is what we expected. Let’s dive into the details.
Firstly, in this case, we need to pass agent_params
to the
constructor (because OneshotAgent
is a controller and not an
Agent
which means it needs an adapter to run. Here we use the
default DefaultOneshotAdapter
:
types = [DefaultOneShotAdapter] * 3
params = [dict(controller_type=GreedyOneShotAgent)] * 3
The real agent type we want is to be passed in controller_type
.
The profile in this case has a different structure than the previous case to match the game description. Other than the production cost, we also need to pass the parameters of Gaussians describing shortfall penalties and disposal costs.
Other than these two differences, the rest is almost the same as in the previous case.
Download Notebook
.