def test_reward():
    env = Molecule()
    assert env._state is None
    assert env.reward() == 0

    env = Molecule(init_mol='C')
    env.reward()
def test_serialization():
    moldqn = DQNFinalState(Molecule(), MorganFingerprints(), batch_size=8)
    res = pkl.dumps(moldqn)
    moldqn_2 = pkl.loads(res)
    assert np.isclose(moldqn.action_network.get_weights()[0],
                      moldqn_2.action_network.get_weights()[0]).all()

    # Test after training
    moldqn.env.reset()
    for i in range(10):
        action, _, _ = moldqn.action()
        new_state, reward, done, _ = moldqn.env.step(action)
        # Save outcome
        moldqn.remember(moldqn.env.state, action, reward,
                        new_state, moldqn.env.action_space.get_possible_actions(), done)

    # Make a training step
    moldqn.train()

    # Test out the serialization
    res = pkl.dumps(moldqn)
    moldqn_2 = pkl.loads(res)
    assert len(moldqn_2.optimizer.get_weights()) == 0
Example #3
0
        } for r in ['ic50', 'QED', 'SA', 'cycles']])
    elif args.reward == "oneshot":
        reward = rewards['oneshot']
    elif args.reward == "tuned":
        reward = LogisticCombination(rewards['ic50'], rewards['oneshot'])
    else:
        raise ValueError(f'Reward function not defined: {args.reward}')
    run_params['maximize'] = reward.maximize

    # Set up environment
    action_space = MoleculeActions(elements,
                                   allow_removal=not args.no_backtrack)
    init_mol = args.initial_molecule
    if init_mol is not None:
        init_mol = convert_smiles_to_nx(init_mol)
    env = Molecule(action_space, reward=reward, init_mol=init_mol)
    logger.debug('using environment: %s' % env)

    # Load in initial molecules, if defined
    init_mols = None
    if args.initial_molecule_file is not None:
        with open(args.initial_molecule_file) as fp:
            init_mols = [convert_smiles_to_nx(x) for x in json.load(fp)]
        logger.info(f'Read in {len(init_mols)} seed molecules')

        # Make sure the molecules only contain elements from the list
        def screen(g):
            atomic_nums = nx.get_node_attributes(g, 'atomic_num').values()
            return all(
                pt.GetElementSymbol(z) in elements for z in atomic_nums
                if z > 1)
Example #4
0
    def run(self):
        # Output files
        output_files = {
            'simulation':
            open(os.path.join(self.output_dir, 'simulation_records.jsonld'),
                 'w')
        }

        # Get the reference energies
        #  TODO (wardlt): I write many of the "send out and wait" patterns, should I build a utility
        #   or is this a fundamental issue?
        self.logger.info(f'Starting Thinker process')
        elems = ['H', 'C', 'N', 'O', 'F']
        for elem in elems:
            self.queues.send_inputs(elem,
                                    spec,
                                    multiplicity[elem],
                                    method='compute_reference_energy')
        ref_energies = {}
        for _ in elems:
            result = self.queues.get_result()
            ref_energies[result.args[0]] = result.value
        self.logger.info(
            f'Computed reference energies for: {", ".join(ref_energies.keys())}'
        )

        # Run the initial molecules
        for mol in self.initial_molecules:
            self.queues.send_inputs(mol,
                                    spec,
                                    ref_energies,
                                    compute_config,
                                    method='compute_atomization_energy')
        for _ in self.initial_molecules:
            result = self.queues.get_result()
            if result.success:
                self.database[result.args[0]] = result.value
            else:
                logging.warning(
                    'Calculation failed! See simulation outputs and Parsl log file'
                )
            print(result.json(), file=output_files['simulation'])
            output_files['simulation'].flush()
        self.logger.info(
            f'Computed initial population of {len(self.database)} molecules')

        # Make the initial RL agent
        agent = DQNFinalState(
            Molecule(reward=SklearnReward(None, maximize=False)),
            MorganFingerprints(),
            q_network_dense=(1024, 512, 256),
            epsilon_decay=0.995)

        for i in range(self.n_evals // self.n_parallel):
            self.logger.info(f'Starting design loop step {i}')
            # Train the machine learning model
            gf = GroupFeaturizer()
            model = Pipeline([('group', gf),
                              ('lasso', BayesianRidge(normalize=True))])
            mols, atoms = zip(*self.database.items())
            model.fit(
                mols,
                atoms)  # negative so that the RL optimizes a positive value
            self.logger.info(
                f'Fit a model with {len(mols)} training points and {len(gf.known_groups_)} groups'
            )

            # Use RL to generate new molecules
            agent.env.reward_fn.model = model
            self.queues.send_inputs(agent, method='generate_molecules')
            result = self.queues.get_result()
            new_molecules, agent = result.value
            self.logger.info(
                f'Generated {len(new_molecules)} candidate molecules')

            # Assign them scores
            self.queues.send_inputs(model,
                                    new_molecules,
                                    method='compute_score')
            result = self.queues.get_result()
            scores = result.value
            self.logger.info(f'Assigned scores to all molecules')

            # Pick a set of calculations to run
            #   Greedy selection for now
            task_options = [{
                'smiles': s,
                'pred_atom': e
            } for s, e in zip(new_molecules, scores)]
            selections = greedy_selection(task_options, self.n_parallel,
                                          lambda x: -x['pred_atom'])
            self.logger.info(f'Selected {len(selections)} new molecules')

            # Run the selected simulations
            for task in selections:
                self.queues.send_inputs(task['smiles'],
                                        spec,
                                        ref_energies,
                                        method='compute_atomization_energy')

            # Wait for them to return
            for _ in selections:
                result = self.queues.get_result()
                if result.success:
                    self.database[result.args[0]] = result.value
                else:
                    logging.warning(
                        'Calculation failed! See simulation outputs and Parsl log file'
                    )
                print(result.json(), file=output_files['simulation'])
                output_files['simulation'].flush()
Example #5
0
def test_moldqn():
    agent = DQNFinalState(
        Molecule(reward=SklearnReward(FakeEstimator().predict)),
        MorganFingerprints())
    output = generate_molecules(agent)
    assert 'C' in output  # There is a (0.25)^10 chance that this will fail
Example #6
0
    def run(self):
        # Get the reference energies
        #  TODO (wardlt): I write many of the "send out and wait" patterns, should I build a utility
        #   or is this a fundamental issue?
        self.logger.info(f'Starting Thinker process')
        elems = ['H', 'C', 'N', 'O', 'F']
        for elem in elems:
            self.queues.send_inputs(elem,
                                    spec,
                                    multiplicity[elem],
                                    method='compute_reference_energy')
        ref_energies = {}
        for _ in elems:
            result = self.queues.get_result()
            ref_energies[result.args[0]] = result.value
        self._ref_energies = ref_energies
        self.logger.info(
            f'Computed reference energies for: {", ".join(ref_energies.keys())}'
        )

        # Run the initial molecules
        for mol in self.initial_molecules:
            self.queues.send_inputs(mol,
                                    spec,
                                    ref_energies,
                                    compute_config,
                                    method='compute_atomization_energy')
        for _ in self.initial_molecules:
            result = self.queues.get_result()
            if result.success:
                self.database[result.args[0]] = result.value
            else:
                logging.warning(
                    'Calculation failed! See simulation outputs and Parsl log file'
                )
            print(result.json(), file=self.output_file)
            self.output_file.flush()
        self.logger.info(
            f'Computed initial population of {len(self.database)} molecules')

        # Make the initial RL agent
        agent = DQNFinalState(
            Molecule(reward=SklearnReward(None, maximize=False)),
            MorganFingerprints(),
            q_network_dense=(1024, 512, 256),
            epsilon_decay=0.995)

        # Launch the "simulator" thread
        design_thread = Thread(target=self.simulation_dispatcher)
        design_thread.start()

        # Perform the design loop iteratively
        step_number = 0
        while len(self.database) < self.n_evals:
            self.logger.info(f'Generating new molecules')
            # Train the machine learning model
            gf = GroupFeaturizer()
            model = Pipeline([('group', gf),
                              ('lasso', BayesianRidge(normalize=True))])
            database_copy = self.database.copy()
            mols, atoms = zip(*database_copy.items())
            model.fit(mols, atoms)
            self.logger.info(
                f'Fit a model with {len(mols)} training points and {len(gf.known_groups_)} groups'
            )

            # Use RL to generate new molecules
            agent.env.reward_fn.model = model
            self.queues.send_inputs(agent,
                                    method='generate_molecules',
                                    topic='ML')
            result = self.queues.get_result(topic='ML')
            new_molecules, agent = result.value
            self.logger.info(
                f'Generated {len(new_molecules)} candidate molecules')
            print(result.json(exclude={'inputs', 'value'}),
                  file=self.rl_records)
            self.rl_records.flush()

            # Assign them scores
            self.queues.send_inputs(model,
                                    new_molecules,
                                    method='compute_score',
                                    topic='ML')
            result = self.queues.get_result(topic='ML')
            scores = result.value
            print(result.json(exclude={'inputs'}), file=self.screen_records)
            self.screen_records.flush()
            self.logger.info(f'Assigned scores to all molecules')

            # Pick a set of calculations to run
            #   Greedy selection for now
            task_options = [{
                'smiles': s,
                'pred_atom': e
            } for s, e in zip(new_molecules, scores)]
            selections = greedy_selection(task_options, self.n_parallel,
                                          lambda x: -x['pred_atom'])
            self.logger.info(f'Selected {len(selections)} new molecules')

            # Add requested simulations to the queue
            for rank, task in enumerate(selections):
                self._task_queue.put(
                    ((-step_number, rank),
                     task['smiles']))  # Sort by recency and then by best
            step_number += 1  # Increment the loop

        self.logger.info('No longer generating new candidates')
        self._gen_done.set()