def test_times_are_calculated(n_outcomes, n_negotiators, n_steps): mechanism = SAOMechanism(outcomes=n_outcomes, n_steps=8) ufuns = MappingUtilityFunction.generate_random(n_negotiators, outcomes=n_outcomes) for i in range(n_negotiators): mechanism.add(AspirationNegotiator(name=f"agent{i}"), ufun=ufuns[i]) assert mechanism.state.step == 0 _strt = time.perf_counter() for _ in range(n_steps): # print(f"Stepping: {_}") mechanism.step() time.sleep(0.01) duration = time.perf_counter() - _strt # assert mechanism.current_step == n_steps assert mechanism._current_offer is not None assert len(mechanism.stats) == 3 stats = mechanism.stats assert "round_times" in stats.keys() assert 0 < sum(stats["round_times"]) < duration assert "times" in stats.keys() assert "exceptions" in stats.keys() assert stats["times"] is not None assert stats["exceptions"] is not None assert len(stats["times"]) == n_negotiators assert len(stats["exceptions"]) == 0 for i in range(n_negotiators): assert 0 < stats["times"][mechanism.negotiators[i].id] < duration assert len(stats["exceptions"][mechanism.negotiators[i].id]) == 0 assert 0 < sum(stats["times"].values()) < duration
def test_pickling_mechanism(tmp_path): import dill as pickle file = tmp_path / "mechanism.pck" n_outcomes, n_negotiators = 5, 3 mechanism = SAOMechanism( outcomes=n_outcomes, n_steps=3, offering_is_accepting=True, avoid_ultimatum=False, ) ufuns = MappingUtilityFunction.generate_random(n_negotiators, outcomes=n_outcomes) for i in range(n_negotiators): mechanism.add(AspirationNegotiator(name=f"agent{i}"), ufun=ufuns[i]) assert mechanism.state.step == 0 with open(file, "wb") as f: pickle.dump(mechanism, f) with open(file, "rb") as f: pickle.load(f) assert mechanism.state.step == 0 mechanism.step() with open(file, "wb") as f: pickle.dump(mechanism, f) with open(file, "rb") as f: pickle.load(f) assert mechanism.state.step == 1
def test_exceptions_are_saved(): n_outcomes, n_negotiators = 10, 2 mechanism = SAOMechanism( outcomes=n_outcomes, n_steps=n_outcomes, ignore_negotiator_exceptions=True ) ufuns = MappingUtilityFunction.generate_random( n_negotiators, outcomes=mechanism.outcomes ) mechanism.add(AspirationNegotiator(name=f"agent{0}"), ufun=ufuns[0]) mechanism.add(MyRaisingNegotiator(name=f"agent{1}"), ufun=ufuns[1]) assert mechanism.state.step == 0 mechanism.step() mechanism.step() mechanism.step() assert mechanism.state.step == 1 assert mechanism._current_offer is not None assert len(mechanism.stats) == 3 stats = mechanism.stats assert "times" in stats.keys() assert "exceptions" in stats.keys() assert stats["exceptions"] is not None assert len(stats["exceptions"]) == 1 # print(stats["exceptions"][mechanism.negotiators[1].id]) assert len(stats["exceptions"][mechanism.negotiators[1].id]) == 1 assert exception_str in stats["exceptions"][mechanism.negotiators[1].id][0] assert len(stats["exceptions"][mechanism.negotiators[0].id]) == 0
def test_mechanism_can_run(n_negotiators): n_outcomes = 5 mechanism = SAOMechanism(outcomes=n_outcomes, n_steps=3) ufuns = MappingUtilityFunction.generate_random(n_negotiators, outcomes=n_outcomes) for i in range(n_negotiators): mechanism.add(AspirationNegotiator(name=f"agent{i}"), ufun=ufuns[i]) assert mechanism.state.step == 0 mechanism.step() mechanism.run()
def test_round_n_agents(n_negotiators): n_outcomes = 5 mechanism = SAOMechanism(outcomes=n_outcomes, n_steps=3) ufuns = MappingUtilityFunction.generate_random(n_negotiators, outcomes=n_outcomes) for i in range(n_negotiators): mechanism.add(AspirationNegotiator(name=f"agent{i}"), ufun=ufuns[i]) assert mechanism.state.step == 0 mechanism.step() assert mechanism.state.step == 1 assert mechanism._current_offer is not None
def on_new_cfp(self, cfp: "CFP"): """Call whenever a CFP is posted. """ if cfp.publisher != self.id: self.request_negotiation( cfp=cfp, negotiator=AspirationNegotiator( ufun=self.output_negotiator_ufun if cfp.is_buy else self.input_negotiator_ufun, aspiration_type=self.agent_aspiration_type, ), )
def test_checkpointing_mechanism(tmp_path): file = tmp_path n_outcomes, n_negotiators = 5, 3 mechanism = SAOMechanism( outcomes=n_outcomes, n_steps=3, offering_is_accepting=True, avoid_ultimatum=False, ) ufuns = MappingUtilityFunction.generate_random(n_negotiators, outcomes=n_outcomes) for i in range(n_negotiators): mechanism.add(AspirationNegotiator(name=f"agent{i}"), ufun=ufuns[i]) assert mechanism.state.step == 0 file_name = mechanism.checkpoint(file) info = SAOMechanism.checkpoint_info(file_name) assert isinstance(info["time"], str) assert info["step"] == 0 assert info["type"].endswith("SAOMechanism") assert info["id"] == mechanism.id assert info["name"] == mechanism.name mechanism, info = SAOMechanism.from_checkpoint(file_name, return_info=True) assert isinstance(info["time"], str) assert info["step"] == 0 assert info["type"].endswith("SAOMechanism") assert info["id"] == mechanism.id assert info["name"] == mechanism.name assert mechanism.state.step == 0 mechanism.step() file_name = mechanism.checkpoint(file) info = SAOMechanism.checkpoint_info(file_name) assert isinstance(info["time"], str) assert info["step"] == 1 assert info["type"].endswith("SAOMechanism") assert info["id"] == mechanism.id assert info["name"] == mechanism.name mechanism, info = SAOMechanism.from_checkpoint(file_name, return_info=True) assert isinstance(info["time"], str) assert info["step"] == 1 assert info["type"].endswith("SAOMechanism") assert info["id"] == mechanism.id assert info["name"] == mechanism.name mechanism.run()
def respond_to_negotiation_request(self, cfp: "CFP", partner: str) -> Optional[Negotiator]: """Called whenever someone (partner) is requesting a negotiation with the agent about a Call-For-Proposals (cfp) that was earlier published by this agent to the bulletin-board Returning `None` means rejecting to enter this negotiation """ if cfp.publisher == self.id and cfp.is_buy: neg_ufun = self.input_negotiator_ufun elif cfp.publisher == self.id and not cfp.is_buy: neg_ufun = self.output_negotiator_ufun else: if self.verbose: print(f'--- WARNING!!! WARNING!!! Rejecting a negotiation --- ') return None return AspirationNegotiator(ufun=neg_ufun, aspiration_type=self.agent_aspiration_type)
def test_loading_laptop_no_names(self, data_folder): domain, agents_info, issues = load_genius_domain_from_folder( os.path.join(data_folder, "Laptop"), force_single_issue=True, keep_issue_names=False, keep_value_names=False, agent_factories=lambda: AspirationNegotiator(), ) # [domain.add(LimitedOutcomesNegotiator(outcomes=n_outcomes) # , ufun=_['ufun']) for _ in agents_info] front, _ = domain.pareto_frontier(sort_by_welfare=True) assert front == [ (0.7715533992081258, 0.8450562871935449), (0.5775524426410947, 1.0), (1.0, 0.5136317604069089), (0.8059990434329689, 0.6685754732133642), ]
def test_mechanism_runall(n_negotiators, oia): n_outcomes = 5 mechanisms = [] for _ in range(10): mechanism = SAOMechanism( outcomes=n_outcomes, n_steps=random.randint(3, 20), offering_is_accepting=oia, avoid_ultimatum=False, ) ufuns = MappingUtilityFunction.generate_random(1, outcomes=n_outcomes) for i in range(n_negotiators): mechanism.add(AspirationNegotiator(name=f"agent{i}"), ufun=ufuns[0]) mechanisms.append(mechanism) states = SAOMechanism.runall(mechanisms) assert len(states) == 10 assert not any(_.running for _ in states)
def test_auto_checkpoint(tmp_path, single_checkpoint, checkpoint_every, exist_ok): import shutil new_folder: Path = tmp_path / unique_name("empty", sep="") new_folder.mkdir(parents=True, exist_ok=True) shutil.rmtree(new_folder) new_folder.mkdir(parents=True, exist_ok=True) filename = "mechanism" n_outcomes, n_negotiators = 5, 3 n_steps = 50 mechanism = SAOMechanism( outcomes=n_outcomes, n_steps=n_steps, offering_is_accepting=True, avoid_ultimatum=False, checkpoint_every=checkpoint_every, checkpoint_folder=new_folder, checkpoint_filename=filename, extra_checkpoint_info=None, exist_ok=exist_ok, single_checkpoint=single_checkpoint, ) ufuns = MappingUtilityFunction.generate_random(n_negotiators, outcomes=n_outcomes) for i in range(n_negotiators): mechanism.add( AspirationNegotiator(name=f"agent{i}"), ufun=ufuns[i], aspiration_type="conceder", ) mechanism.run() if 0 < checkpoint_every <= n_steps: if single_checkpoint: assert len(list(new_folder.glob("*"))) == 2 else: assert len(list(new_folder.glob("*"))) >= 2 * ( max(1, mechanism.state.step // checkpoint_every) ) elif checkpoint_every > n_steps: assert len(list(new_folder.glob("*"))) == 2 else: assert len(list(new_folder.glob("*"))) == 0
def test_mechanism_runs_with_offering_not_accepting(n_negotiators, oia): n_outcomes = 5 mechanism = SAOMechanism( outcomes=n_outcomes, n_steps=3, offering_is_accepting=oia, avoid_ultimatum=False ) ufuns = MappingUtilityFunction.generate_random(1, outcomes=n_outcomes) for i in range(n_negotiators): mechanism.add(AspirationNegotiator(name=f"agent{i}"), ufun=ufuns[0]) assert mechanism.state.step == 0 mechanism.step() assert mechanism._current_proposer.name == "agent0" assert mechanism._n_accepting == n_negotiators + int(oia) - 1 assert (mechanism.agreement is not None) is oia if mechanism.agreement is not None: return mechanism.step() assert mechanism._current_proposer.name == "agent0" assert mechanism._n_accepting == n_negotiators assert mechanism.agreement is not None
def test_single_agreement_gets_one_agreement(n_negs, strict): from negmas.mechanisms import Mechanism from negmas.sao import AspirationNegotiator, SAOSingleAgreementRandomController c = SAOSingleAgreementRandomController(strict=strict) negs = [ SAOMechanism( issues=[Issue((0.0, 1.0), "price")], n_steps=50, outcome_type=tuple ) for _ in range(n_negs) ] for i, neg in enumerate(negs): neg.add( AspirationNegotiator(aspiration_type="linear", name=f"opponent-{i}"), ufun=LinearUtilityFunction(weights=[1.0]), ) neg.add(c.create_negotiator(name=f"against-{i}")) Mechanism.runall(negs, True) agreements = [neg.state.agreement for neg in negs] if strict: # assert that the controller never had multiple agreements assert sum(_ is not None for _ in agreements) == 1 else: # assert that the controller never accepted twice. It may still have multiple agreements though assert ( len( [ neg.state.agreement for neg in negs if neg.state.agreement is not None and neg.state.current_proposer.startswith("opponent") ] ) < 2 )
def test_sync_controller(n_negotiations, n_negotiators, oia): n_outcomes = 2 mechanisms = [] controller = MySAOSync() for i in range(n_negotiators): mechanisms.append( SAOMechanism( outcomes=n_outcomes, n_steps=5, offering_is_accepting=oia, avoid_ultimatum=False, ) ) ufuns = MappingUtilityFunction.generate_random( n_negotiators, outcomes=n_outcomes ) for i in range(n_negotiators): mechanisms[-1].add(AspirationNegotiator(name=f"agent{i}"), ufun=ufuns[i]) mechanisms[-1].add(controller.create_negotiator()) states = SAOMechanism.runall(mechanisms) assert all(_.agreement is not None for _ in states)
def step(self): """Called at every production step by the world""" # Book keeping for p in range(0, self.num_intermediate_products + 2): self.storage_history[p].append(self.awi.state.storage[p]) self.wallet_history.append(self.awi.state.wallet) # ---------------- MPNVM STUFF -------- # Plan how many inputs to go for. We ask the brain for the number of inputs. If the brain could not find data, we go for a fixed number. plan_for_inputs = self.agent_brain.get_plan_for_inputs(self.current_step + 1, self.verbose) if self.agent_brain.there_is_data else [self.fixed_number_of_inputs] # Post a call for proposal to buy inputs using the plan computed before. We limit the call for buy stuff up to 15 steps before the end of game. # @todo There could be a further optimization problem here, as in, how many CFPs to post? At the moment we just post one. if self.current_step <= self.limit_post_cfps and self.storage_history[self.output_index][-1] <= self.limit_sign_storage: self.awi.register_cfp(CFP(is_buy=True, publisher=self.name, product=self.input_index, # @todo The time of negotiation matters a lot for longer chains. For chain of size 4, +5, +15 worked. time=(self.current_step + self.cfp_time_lower_bound, self.current_step + self.cfp_time_upper_bound), unit_price=(0.0, self.expected_catalog_prices[self.input_index]), quantity=(max(1, plan_for_inputs[0] - self.cfp_qtty_range_width), plan_for_inputs[0] + self.cfp_qtty_range_width))) # Send all inputs to production to get outputs. We always turn every input into output. We don't hold on to inputs. schedule_for_production = 0 for l in range(0, 10): if self.storage_history[self.input_index][-1] > 0 and \ schedule_for_production <= self.storage_history[self.input_index][-1] and \ self.awi.current_step < self.awi.n_steps - 1: self.awi.schedule_production(l, self.awi.current_step) self.simulator.schedule(Job(profile=l, time=self.awi.current_step, line=-1, action='run', contract=None, override=False)) schedule_for_production += 1 # Read all the CFPs and engage in negotiations with agents that want to buy our output. the_cfps = self.awi.bb_query('cfps', None) if the_cfps: for i, c in the_cfps.items(): c: CFP # Negotiate about outputs. It is highly unlikely the agent can have any output product ready before parameter self.start_sell_negotiation_bound if c.publisher != self.id and c.is_buy and c.product == self.output_index and c.min_time >= self.start_sell_negotiation_bound: self.request_negotiation(cfp=c, negotiator=AspirationNegotiator(ufun=self.output_negotiator_ufun, aspiration_type=self.agent_aspiration_type)) # @todo Negotiate about inputs. This is currently not active, as in the case of inputs our agent is proactive. Should we activate it? # elif not c.is_buy and c.product == self.input_index: # self.request_negotiation(cfp=c, negotiator=AspirationNegotiator(name="my-goog-buyer", ufun=self.input_negotiator_ufun)) # ---------------- MIDDLE MAN STUFF -------- # @todo Add parameters for the ranges over which we post CFPs for the middle man. if self.middle_man_active: # First, post CFP to buy and sell stuff, up to time given by parameter self.limit_post_cfps. if self.current_step <= self.limit_post_cfps: for p in self.middle_man_products: # Post a CFP to buy stuff to be later resold self.awi.register_cfp(CFP(is_buy=True, publisher=self.name, product=p, time=(self.current_step + self.cfp_time_lower_bound, self.current_step + self.cfp_time_upper_bound), unit_price=(0.0, self.expected_catalog_prices[p]), quantity=(1, 5))) # Post a CFP to sell stuff self.awi.register_cfp(CFP(is_buy=False, publisher=self.name, product=p, time=(self.current_step + self.cfp_time_lower_bound, self.current_step + self.cfp_time_upper_bound), unit_price=(3.5, 25.5), quantity=(1, 5))) if the_cfps: for i, c, in the_cfps.items(): c: CFP # Make sure we don't respond to ourselves. if c.publisher != self.id: # Respond to CFPs when we try to buy stuff for the middle man if c.product in self.middle_man_products and not c.is_buy: # print(f'Responding to a cfp from {c.publisher} to buy {c.product}. Here it is: CFP = {c}') self.request_negotiation(cfp=c, negotiator=AspirationNegotiator(ufun=self.input_negotiator_ufun, aspiration_type=self.agent_aspiration_type)) # Respond to CFPs when we try to sell stuff for the middle man if c.product in self.middle_man_products and c.is_buy: # print(f'Responding to a cfp to sell CFP for product {c.product} by {c.publisher}, CFP = {c}') self.request_negotiation(cfp=c, negotiator=AspirationNegotiator(ufun=self.output_negotiator_ufun, aspiration_type=self.agent_aspiration_type, max_aspiration=0.95)) # DEBUG INFO if self.verbose: # Print some debug info for development purposes. self.print_debug_info(plan_for_inputs, self.storage_history[self.input_index][-1], self.storage_history[self.output_index][-1]) # Save results of the hyper-parameter optimization if self.current_step + 1 == self.awi.n_steps: if not os.path.exists('my_results'): os.makedirs('my_results') results_file_name = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)) with open(f'my_results/{results_file_name}.dat', 'w') as file: file.write(f'{self.marginal_calculation_on_sign_contract},' f'{self.limit_sign_storage},' f'{self.limit_number_buys_contracts_at_t},' f'{self.limit_number_sales_contracts_at_t},' f'{self.cfp_qtty_range_width},' f'{self.input_index},{self.output_index},' f'{self.num_intermediate_products},' f'{self.wallet_history[-1]}')
def test_aspiration_continuous_issues( n_negotiators, n_issues, presort, randomize_offers ): for _ in range(5): mechanism = SAOMechanism( issues=[Issue(values=(0.0, 1.0), name=f"i{i}") for i in range(n_issues)], n_steps=10, ) ufuns = [ LinearUtilityFunction( weights=[3.0 * random.random(), 2.0 * random.random()], reserved_value=0.0, ) for _ in range(n_negotiators) ] best_outcome = tuple([1.0] * n_issues) worst_outcome = tuple([0.0] * n_issues) i = 0 assert mechanism.add( AspirationNegotiator( name=f"agent{i}", presort=presort, randomize_offer=randomize_offers, ufun=ufuns[i], ufun_max=ufuns[i](best_outcome), ufun_min=ufuns[i](worst_outcome), ) ), "Cannot add negotiator" for i in range(1, n_negotiators): assert mechanism.add( AspirationNegotiator( name=f"agent{i}", presort=presort, randomize_offer=randomize_offers, ufun_max=ufuns[i](best_outcome), ufun_min=ufuns[i](worst_outcome), ), ufun=ufuns[i], ), "Cannot add negotiator" assert mechanism.state.step == 0 agents = dict(zip([_.id for _ in mechanism.negotiators], mechanism.negotiators)) offers = defaultdict(list) while not mechanism.state.ended: mechanism.step() for neg_id, offer in mechanism.state.new_offers: assert neg_id in agents.keys() neg = agents[neg_id] prev = offers[neg_id] last_offer = prev[-1] if len(prev) > 0 else float("inf") if randomize_offers: assert neg.utility_function(offer) <= neg.utility_function( best_outcome ) else: assert neg.utility_function(offer) <= last_offer if not presort: assert ( -neg.tolerance <= ( neg.utility_function(offer) - neg.aspiration( (mechanism.state.step) / mechanism.n_steps ) * neg.utility_function(best_outcome) ) < pow(neg.tolerance, 0.5 / neg.n_trials) + neg.tolerance ) # else: # assert -neg.tolerance <= ( # neg.utility_function(offer) # - neg.aspiration( # (mechanism.state.step - 1) / mechanism.n_steps # ) # * neg.utility_function(best_outcome) # ) offers[neg_id].append(neg.utility_function(offer))