retrolca is a toolkit for transforming retrosynthesis pathways into linked
process chains using the openLCA IPC API.
The process generation with retrolca can be configured in many ways, including
the maximum depth and number of variants of the generated process chains, the
retrosynthesis backend, naming service, caching, and process features like
balancing with waste flows or linking production processes.
The figure below shows an example of a process chain generated by retrolca. In
this example, four intermediate processes are created and then automatically
linked to ecoinvent background data.
retrolca is a Python project which can be quickly set up with
uv. When you download the project, you can then
quickly install the required interpreter and dependencies via uv sync:
# open a terminal and navigate to the project folder
cd retrolca
# download the Python interpreter and dependencies
uv sync
# modify and run the scripts in this project
uv run examples/...pyAs said above, retrolca communicates with openLCA via the openLCA IPC
interface. In openLCA, activate
the database where you want to generate the processes and start the IPC server
via Tools > Developer tools > IPC Server. On the retrolca side, you then
initialize the IpcContext which checks the connected database (see below) and
creates an index of the required flow properties and chemical flows. It is then
used to link and create datasets in openLCA:
import retrolca as r
ctx, err = r.IpcContext.of(ipc.Client())
if err:
print("Failed to initialize the IPC context: ", err)Configuring the provider selection
If the database contains several processes producing the same chemical
(identified by a SMILES code), the IpcContext uses a provider selector to
decide which one is linked as provider when a process for that chemical is
required. By default this is a DefaultProviderSelector that scores the
candidate processes by their location (preferring GLO) and by their name
(processes named market for ... or ... production score higher). A custom
selector can be passed to IpcContext.of; a selector receives the candidate
providers of a chemical and returns the one to link:
class LocalProviders(r.ProviderSelector):
def select(self, providers):
for p in providers:
if p.provider and p.provider.location == "DE":
return p
return providers[0] if providers else None
ctx, err = r.IpcContext.of(ipc.Client(), provider_selector=LocalProviders())Returning None skips linking, so the retrosynthesis builder would instead
generate a new production process for that chemical.
retrolca can be used with any openLCA database, but the flow properties Mass
and Chemical amount must be present (as provided by the openLCA reference
data). If no suitable background processes are available, retrolca creates the
required product flows and processes for the synthesis routes.
However, its full potential is realized with a background database containing
chemical production processes, such as ecoinvent. When the product flows of
these processes have SMILES codes attached, retrolca can link them as
production processes for reactants when generating synthesis routes.
Adding SMILES codes to product flows in openLCA
Currently, retrolca reads the SMILES codes from the additional properties of
product flows, checking possible entries under SMILES, Absolute-SMILES, and
Connectivity-SMILES (in this order):
In openLCA, you can try the PubChem tool to get the SMILES code of a chemical:
retrolca also contains tooling to enrich a database with SMILES codes and
other chemical properties from PubChem. If it can find the corresponding data on
PubChem, the pubchem decorator will also add Chemical amount as a flow
property (using the molar mass to calculate the conversion factor).
For example, this script would try to decorate all flows with manufacture of basic chemicals in their category path with chemical properties from PubChem:
import olca_ipc as ipc
import retrolca as retro
import retrolca.pubchem as pub
client = ipc.Client()
ctx, _ = retro.IpcContext.of(client)
pub.IpcFlowDecorator(ctx).try_all(in_path="manufacture of basic chemicals")Once a database is decorated, you can persist the collected PubChem decorations to JSON and later apply them to another database.
pub.dump_decorations(ctx, path)
pub.load_decorations(ctx, path)A full example can be found in the pubchem_decorate_flows.py
example
Using PubChem is only one possible way to add SMILES codes to chemical products
in openLCA. You can of course use other data sources for this in the same ways.
retrolca just needs to find flows with SMILES codes in order to link them in
process chains.
retrolca can use different retrosynthesis tools. Currently, it supports ASKCOS
and AiZynthFinder, but another retrosynthesis backend could be easily added. It
just needs to implement the RetroTool protocol, providing an expand method
to generate possible reactions for a given SMILES code of a chemical:
class RetroTool(Protocol):
id: str
def expand(self, smiles: str) -> Res[list[Reaction]]: ...Setting up AiZynthFinder
To setup AiZynthFinder as local retrosynthesis tool, install the project
dependencies via uv sync as described above. You will then have a
download_public_data models tool in your local Python environment, that can
download the public AiZynthFinder models. Create a models folder and set this
as the download target:
# create the models folder
mkdir models
# download the public AiZynthFinder models
./.venv/bin/download_public_data models
# or on Windows
.\.venv\Scripts\download_public_data.exe modelsThis will download the models and generate a models/config.yml file with which
you can initialize the ZynthTool then, which wraps the AiZynthFinder in the
RetroTool protocol. See the
examples/zynthfinder_example.py for a full
example.
import retrolca as r
# finds the models/config.yml file relative to the current script file
config = Path(__file__).parent.parent / "models/config.yml"
# initializes AiZynthFinder via the ZynthTool wrapper
tool = r.ZynthTool(config)Connecting to ASKCOS
retrolca can connect to an ASKCOS server instance via its REST API. For this,
you need to provide the login data of a valid user account as a JSON file with
the following format:
{
"endpoint": "https://your-askcos-instance/api",
"user": "your-user",
"password": "your-password"
}You can also use the public ASKCOS instance and enter
https://askcos.mit.edu/api as the API endpoint. You can put that file for
example under auth/askcos_login.json, then load it and connect to ASKCOS like
this
import retrolca as r
login = r.AskcosLogin.from_file("auth/askcos_login.json")
client = r.AskcosClient(login)
# close the client when you are done
client.close()See the examples/zynthfinder_example.py for a
full example. ASKCOS has quite some configuration options which retrolca can
pass to the API. If you just want to change the model and keep the default
options, just pass the model name to the client. You can use the constants
retrolca defines for this, or just pass the name of the model to the client:
# ...
client = r.AskcosClient(login, model=r.AskcosModel.PISTACHIO)You can also pass in a dictionary with all possible configuration options when constructing the client:
# ...
client = r.AskcosClient(login, options=
{
"retro_backend_options": [
{
"retro_backend": "template_relevance",
"retro_model_name": "reaxys",
"max_num_templates": 1000,
"max_cum_prob": 0.995,
"attribute_filter": [],
"threshold": 0.3,
"top_k": 10,
}
],
"use_fast_filter": True,
"fast_filter_threshold": 0.75,
"retro_rerank_backend": "relevance_heuristic",
"atom_map_backend": "rxnmapper",
# ...
},
)See the official ASKCOS API documentation for the full configuration options:
https://askcos.mit.edu/docs#/tree-search/askcos_run_retro_expansion_async
We call the /api/tree-search/expand-one/call-async endpoint with the provided
options. The schema for the options of that method is defined under
#components/schemas/ExpandOneInput in the API documentation.
Caching retrosynthesis results
A retrosynthesis tool can be wrapped in a CachingRetroTool. This will then
store the returned results of the tool in a database and checks for stored
results before redirecting to the wrapped tool. This can be very useful to avoid
redundant computations. CachingRetroTool provides the standard tool protocol
of retrolca and can be used everywhere where this protocol is expected.
caching_tool = r.CachingRetroTool("out/cached_reactions.db", tool)
caching_tool.expand("CCOP(=O)(OCC)OCC")
# ...A retrosynthesis tool typically only returns the SMILES codes of the reactants
for a given SMILES code of a product. For creating product flows in openLCA for
these SMILES code, we need a service that translates SMILES codes to names. By
default retrolca uses CIRpy, a Python package
that calls CIR, for resolving
chemical names:
import retrolca as r
cir = r.CIR()
name = cir.get_name("CCCCN1CCCC1=O")However, in the same way as for the retrosynthesis tool, there is a common
protocol NamingService that should be easy to implement for using an
alternative service. Also, like for the retroysnthesis it is possible and
recommended to use the CachingNamingService to wrap the naming service to
avoid unnecessary requests (APIs like CIR typically have request limits):
import retrolca as r
cir = r.CIR()
caching_cir = r.CachingNamingService("out/cached_names.db", cir)
name = caching_cir.get_name("CCCCN1CCCC1=O")The ProcessBuilder is the central component of retrolca which combines the
described tools above to recursively build a process chain that models the
production of a given chemical based on possible chemical reactions. Here is a
minimal example how it can be used:
import olca_ipc as ipc
import retrolca as r
ctx, _ = r.IpcContext.of(ipc.Client())
tool = r.ZynthTool("models/config.yml")
naming = r.CachingNamingService("out/cir_names.db", r.Cir())
builder = r.ProcessBuilder(
ctx,
tool,
max_levels=3,
max_variants=2,
gen_process="83083965-4104-4c87-88af-bc200b6a520c",
bal_process="4ad86534-aba4-3106-ac12-81e322834704",
naming=naming,
)
builder.build(
"CCCCN1CCCC1=O",
name="1-butylpyrrolidin-2-one",
category="Retrosynthesis/Inbox",
)This example should then generate the following processes:
For each generated process, retrolca also creates a reaction image
showing the reactants together with the product, making it easy to
review the synthesis route later in openLCA.
The parameters of the `ProcessBuilder`
When you create a ProcessBuilder it takes the following parameters:
ctx(required) - The openLCA IPC context, as described above.tool(required) - The retrosynthesis tool, as described above.max_levels(optional, default is3) - The maximum number of levels or the maximum depth of the process chain the process builder can build. When the process builder transforms a chemical reaction into a process, it first checks if the respective reactants already exist as product flows in the databases, and if yes, it links the respective processes that produce these products as providers of the inputs. If not, it recursively generates processes for these inputs and links them.max_variants(optional, default is1) - For a chemical product, the retrosynthesis tool can return multiple possible chemical reactions with a score for probability and feasibility. It then selects the reaction with the best scores and generates a process for this. For a product input, this process is then linked as a provider. Ifmax_variants > 1, it will also generate processes for the next best possible reactions. Note that in this case, it can quickly result in a large number of processes, since processes are again generated for the inputs of these alternatives according to the same rules of the builder.gen_process(optional, default isNone) - The ID of a generic production process can be provided that is linked to every process the builder generates. This process needs to describe the generic production of chemicals per mass of product output (this is also known as the Gendorf Approach). It is then linked with 1 kg as input for every generated process, as the builder always generates processes related to 1 kg of product output.bal_process(optional, default isNone) - In the generated process, the mass of the reactant inputs is in most cases smaller than the mass of the product output, so< 1 kg. A balancing process can be provided, that is linked with that difference to an output. In case the provided balancing process is a waste treatment process, the balancing flow will be a waste output linked to that waste treatment process. If a product process is provided, the balancing flow will be added as an avoided product output linked to that process.naming(optional, default is aCIRinstance) - The naming service as described above.
As shown in the example above, the build method is then used to generate the
process chains. The same builder instance can be used to build process chains
for different chemicals. The build method takes the following arguments:
smiles_code(required) - The SMILES code of the chemical for which the chain (tree) of production processes should be created.name(optional, default isNone) - The name of the root chemical. If not provided, the naming service of the builder will be used to determine the name but this is often not necessary as the name of this chemical is typically known.category(optional, default isNone) - An optional category path under which the generated processes and products will be stored. Additionally, the processes will be stored under sub-categories for the levels of these processes.
As described in the details above, the max_levels controls the depth of the
generated process chain. For the continuation of process chain creation, the
expand_process method can be used, which takes the ID of the process of which
the supply chain should be completed as argument:
builder.expand_process(
"c74ccb59-b79a-4d51-8df0-33f7320f4b53",
category="Retrosynthesis/Inbox",
)When you use the multiple-variants feature of the process builder (which often makes sense as the reaction of the highest score is not necessarily the most realistic option), many processes could be created. The intended workflow is for you to review and edit these generated processes, link them in product systems or move them from some inbox category to another category of the database. After this, you can run the cleanup.jy script directly in openLCA to delete the other generated processes.


