Name: Towards AI Legal Name: Towards AI, Inc. Description: Towards AI is the world's leading artificial intelligence (AI) and technology publication. Read by thought-leaders and decision-makers around the world. Phone Number: +1-650-246-9381 Email: pub@towardsai.net
228 Park Avenue South New York, NY 10003 United States
Website: Publisher: https://towardsai.net/#publisher Diversity Policy: https://towardsai.net/about Ethics Policy: https://towardsai.net/about Masthead: https://towardsai.net/about
Name: Towards AI Legal Name: Towards AI, Inc. Description: Towards AI is the world's leading artificial intelligence (AI) and technology publication. Founders: Roberto Iriondo, , Job Title: Co-founder and Advisor Works for: Towards AI, Inc. Follow Roberto: X, LinkedIn, GitHub, Google Scholar, Towards AI Profile, Medium, ML@CMU, FreeCodeCamp, Crunchbase, Bloomberg, Roberto Iriondo, Generative AI Lab, Generative AI Lab Denis Piffaretti, Job Title: Co-founder Works for: Towards AI, Inc. Louie Peters, Job Title: Co-founder Works for: Towards AI, Inc. Louis-François Bouchard, Job Title: Co-founder Works for: Towards AI, Inc. Cover:
Towards AI Cover
Logo:
Towards AI Logo
Areas Served: Worldwide Alternate Name: Towards AI, Inc. Alternate Name: Towards AI Co. Alternate Name: towards ai Alternate Name: towardsai Alternate Name: towards.ai Alternate Name: tai Alternate Name: toward ai Alternate Name: toward.ai Alternate Name: Towards AI, Inc. Alternate Name: towardsai.net Alternate Name: pub.towardsai.net
5 stars – based on 497 reviews

Frequently Used, Contextual References

TODO: Remember to copy unique IDs whenever it needs used. i.e., URL: 304b2e42315e

Resources

Take our 85+ lesson From Beginner to Advanced LLM Developer Certification: From choosing a project to deploying a working product this is the most comprehensive and practical LLM course out there!

Publication

Solving Facility Location
Data Science   Latest   Machine Learning

Solving Facility Location

Last Updated on December 13, 2024 by Editorial Team

Author(s): Saif Ali Kheraj

Originally published on Towards AI.

We will start with one important use case to solve a facility location problem. Suppose we have a service-based company named MMM that sells different products to customers across various regions. Every year, MMM faces tough decisions about where to set up new offices and how many engineers to host in each office. The goal is to make these choices in a way that reduces service costs, office rent, and utilities expenses while still meeting all customer demands. By using data science and prescriptive analytics, MMM can carefully analyze costs, capacities, and customer locations to find the best facility setup that saves money and delivers services efficiently.

Figure 1 by Author: Sample Current Cost
Figure by Author: Locations where company currently operate
Figure by Author: Cost Breakup by location

For example, when the CEO sees these costs, he may want to optimize them, reducing total expenses while still meeting all demand constraints and making other necessary decisions.

Introduction

Service Cost: This is the direct cost associated with providing after-sales services, such as labor, maintenance, and support services.

  • New York has the highest service cost at $12,500,000. This is likely due to higher wages and operational costs in a metropolitan area where skilled labor demands a premium.
  • Other cities, like Baltimore and Richmond, have significantly lower service costs because of lower wage scales and potentially less demand for specialized services.

Office Rent: This cost reflects the expense of leasing office space needed to support operations. New York again stands out with an office rent of $45,000,000, highlighting the high real estate costs in urban centers, particularly in prime locations. Boston and Washington D.C. also show considerable office rent, but significantly lower than New York. In contrast, cities like Virginia Beach and Raleigh have much lower rent costs, making them more affordable locations for businesses.

Utility Fee: Utility costs encompass expenses for electricity, water, heating, and other essential services required to maintain the facility.

  • City Variations:
  • The utility fees are generally lower compared to office rents, but still reflect the local cost of utilities. For example, New York has a utility fee of $10,000,000, while Virginia Beach has the lowest at $200,000, indicating that utility costs vary with local energy rates and usage levels.

As a manager or CEO, one must find ways to reduce these high costs. Maybe closing a certain location would save on office rent, but then the service costs might increase. Thus, there’s a natural trade-off between these two factors.

  1. If we have fewer facilities then we will have higher service cost
  2. If we have more facilities, then we will have lower service cost.

Not only that, but there are many other factors involved. For example, if we close one facility, the customers using that facility need to be reassigned elsewhere, and engineers must also be reassigned. The situation also depends on an environment that keeps changing. For instance, in the future a customer might need fewer services, or overall demand could shift. When these changes occur, we simply update our input data to reflect the new conditions.

Mathematical Optimization Process

Main goal is to minimize the total operational cost of setting up and maintaining facilities to serve customers across various locations. This total cost includes:

  1. Office Rent Costs for facilities at different scale levels (small, medium, large).
  2. Traveling Costs for engineers to serve customers from facilities.
  3. Engineer Hosting Costs at each facility, representing the cost of maintaining engineers at these locations.

Decision Variables

Decision Variable depends on:

  • Facility Presence: Whether to establish a facility in a given location.
  • Facility Scale: The size and capacity of the facility if established.
  • Engineer Allocation: How many engineers will be assigned to each facility.
  • Customer Assignment: Which customers are served by which facilities.
  1. Facility Location and Scale Decision (x_jk): Whether to establish a facility at a particular location j and scale level k. If yes, this variable will be set to 1, otherwise 0.
Figure by Author: Explaining assignment or selection of scale within location

2. Customer Assignment Decision (y_ij): Whether a customer i is assigned to be served by a facility at location j. This binary variable is 1 if the customer is assigned to the facility, and 0 otherwise. We will use linking constraint to ensure a customer i can only be assigned to a facility location j if that facility has been established.

3. Engineer Allocation Decision (w_j): The number of engineers allocated to each facility location j. In the constraints part, we will add capacity constraint as well.

Code to define Decision Variables

import pulp

# Step 1: Initialize the LP problem
problem = pulp.LpProblem("Facility_Location_Problem", pulp.LpMinimize)

# Step 2: Define Decision Variables
# Binary decision variable x_jk: 1 if a facility j is built at scale k, 0 otherwise
x_jk = pulp.LpVariable.dicts("x_jk", ((loc, scale) for loc, scale in f_jk), cat='Binary')

# Binary decision variable y_ij: 1 if customer i is served by facility j, 0 otherwise
y_ij = pulp.LpVariable.dicts("y_ij", ((cust, loc) for cust in a_ij for loc in a_ij[cust]), cat='Binary')

# Engineer hosting variable w_j (number of engineers assigned to location j)
w_j = pulp.LpVariable.dicts("w_j", (loc for loc in c_j), lowBound=0, cat='Integer')

Before discussing about objective function, we need to discuss input parameters. These parameters provide the necessary data on which the optimization is based, such as costs, capacities, and demands.

Key Input Parameters

Facility-Scale Rent Cost (f_jk):

  • Annual rent cost for each facility location j at scale level k (e.g., small, medium, large).
  • This will represent the cost component in the objective function related to choosing a facility and its scale.
Figure by Author: All Possible Rent available based on Location and scale (Note: The data shown is partial; the actual dataset contains additional rows and details.)

As shown in the diagram, we have various scale options for rent, each offering a different combination of rent.

Customer Service Demand (h_i):

  • The annual number of services required for each customer i.
  • This parameter determines the amount of service demand that needs to be met by the facilities.
Figure by Author: Annual Demand by customers (Note: The data shown is partial; the actual dataset contains additional rows and details.)

Travel Cost (d_ij​):

  • The cost per service for an engineer to travel from facility location j to customer i.
  • This parameter influences the travel cost component in the objective function, affecting which facilities serve which customers.
Figure by Author: Travel Cost (Note: The data shown is partial; the actual dataset contains additional rows and details.)

Engineer Hosting Cost ( cj):

  • The cost for hosting one engineer at each facility location j.
  • This component of the objective function will capture the expense of having engineers stationed at a particular facility.
  • Because labor costs vary from one location to another, we need to factor this into our decisions as well.
Figure by Author: Cost to host one engineer in a particular location (Note: The data shown is partial; the actual dataset contains additional rows and details.)

Maximum Number of Engineers (m_jk):

  • The maximum capacity (number of engineers) that can be hosted at facility j depending on its scale level k.
  • This parameter ensures that facilities are not over-allocated beyond their capacity.
Figure by Author: Maximum number of engineers that can be hosted based on location and scale (Note: The data shown is partial; the actual dataset contains additional rows and details.)

Service Capacity per Engineer (s):

  • The number of services that a single engineer can complete in a year.
  • This value is crucial for understanding how many engineers are required to meet the customer demand allocated to each facility.
  • Let us set s=200

Customer-Facility Reachability (a_ij​):

  • A binary parameter that indicates whether a facility location j can serve customer i (1 if reachable, 0 otherwise).
  • This parameter adds practical constraints, restricting which facilities can realistically serve each customer.
Figure by Author: Where customer i can by reached by location j (Note: The data shown is partial; the actual dataset contains additional rows and details.)

This matrix provides:

  • Regional clustering for flexibility in customer assignment.
  • Coverage redundancy where multiple facilities can serve the same customer, allowing for reassignment if needed.

Code for loading input data

import pandas as pd

def clean_numeric(value):
if isinstance(value, str):
try:
return float(value.replace(',', ''))
except ValueError:
return value
return value

# Load CSV files into pandas dataframes
cost_to_host_engineer_location = pd.read_csv('cost_to_host_engineer_location.csv', skipinitialspace=True)
customer_location_reachability = pd.read_csv('customer_location_reachability.csv', skipinitialspace=True)
customer_service_required = pd.read_csv('customer_service_required.csv', skipinitialspace=True)
location_scale_rent = pd.read_csv('location_scale_rent.csv', skipinitialspace=True)
location_to_customer_cost = pd.read_csv('location_to_customer_cost.csv', skipinitialspace=True)
max_num_engineer_hosted = pd.read_csv('max_num_engineer_hosted.csv', skipinitialspace=True)

# Apply the cleaning function to numeric columns only
cost_to_host_engineer_location['Cost for Hosting One Engineer'] = cost_to_host_engineer_location['Cost for Hosting One Engineer'].apply(clean_numeric)
location_scale_rent['Rent'] = location_scale_rent['Rent'].apply(clean_numeric)
numeric_cols = location_to_customer_cost.columns[1:] # Skip the first column (Customer)
location_to_customer_cost[numeric_cols] = location_to_customer_cost[numeric_cols].applymap(clean_numeric)

# Constant: Number of services an engineer can complete per year
s = 200

# Display each dataframe to ensure they are loaded correctly
print("Cost to Host Engineer Location:\n", cost_to_host_engineer_location.head(), "\n")
print("Customer Location Reachability:\n", customer_location_reachability.head(), "\n")
print("Customer Service Required:\n", customer_service_required.head(), "\n")
print("Location Scale Rent:\n", location_scale_rent.head(), "\n")
print("Location to Customer Cost:\n", location_to_customer_cost.head(), "\n")
print("Max Number of Engineers Hosted:\n", max_num_engineer_hosted.head(), "\n")

# Convert each dataframe into a dictionary to use as parameters in the model

# 1. Cost to Host Engineer in each Location (c_j)
# c_j represents the annual cost for hosting one engineer at location j
c_j = dict(zip(cost_to_host_engineer_location['Location'], cost_to_host_engineer_location['Cost for Hosting One Engineer']))

# 2. Customer Location Reachability (a_ij)
# a_ij represents whether customer i can be served by facility j (1 if possible, 0 otherwise)
a_ij = customer_location_reachability.set_index('Customer').T.to_dict()

# 3. Customer Service Requirement (h_i)
# h_i represents the annual number of services required for customer i
h_i = dict(zip(customer_service_required['Customer'], customer_service_required['Annual # of Services Required']))

# 4. Location Scale Rent (f_jk)
# f_jk represents the annual office rent for facility j at scale k (small, medium, large)
f_jk = location_scale_rent.set_index(['Location', 'Scale'])['Rent'].to_dict()

# 5. Location to Customer Cost (d_ij)
# d_ij represents the cost per service for an engineer to travel between facility j and customer i
d_ij = location_to_customer_cost.set_index('Customer').T.to_dict()

# 6. Maximum Number of Engineers Hosted at a location (m_jk)
# m_jk represents the maximum number of engineers that can be hosted at location j with scale level k (small, medium, large)
m_jk = max_num_engineer_hosted.set_index('Location').T.to_dict()

print("Cost to Host Engineer Dictionary (c_j):\n", c_j, "\n")
print("Reachability Dictionary (a_ij):\n", a_ij, "\n")
print("Customer Service Requirement Dictionary (h_i):\n", h_i, "\n")
print("Location Scale Rent Dictionary (f_jk):\n", f_jk, "\n")
print("Location to Customer Cost Dictionary (d_ij):\n", d_ij, "\n")
print("Max Number of Engineers Hosted Dictionary (m_jk):\n", m_jk, "\n")

Summary of input Variables:

1. Facility Costs

  • f_jk​: Annual office rent for facility j at scale k (small, medium, large).
  • c_j​: Cost for hosting one engineer at facility j.

2. Customer Requirements

  • h_i: Annual number of services required for customer i(this is demand).
  • d_ij​: Cost per service for an engineer to travel between facility j and customer i.

3. Facility Capacity

  • m_jk​: Maximum number of engineers that can be hosted at facility j with scale k (small, medium, large).
  • s: Number of services an engineer can complete per year.

4. Reachability

  • a_ij: Reachability indicator for whether customer i can be served by facility j (1 if reachable, 0 otherwise).

Objective Function

The objective is to minimize cost, which can be divided into three main components, each with its own trade-offs as we discussed:

  • Office Rent
  • Travelling Cost
  • Utility Cost
Figure by Author: Objective Function equation to represent total cost

The goal is to minimize the sum of these costs. The first part represents the total office rent cost across all facility locations. The second part represents the total traveling cost for engineers to reach customers. In this second part, we use the cost to serve a customer from location j (dij​), the number of services required by that customer (hi​), and yij (a binary variable indicating if facility location j is assigned to that customer). The third part represents the cost of hosting engineers, which depends on the cost per engineer at location j (cj​) and the number of engineers assigned there (wj).

Objective function Code


# 1. Office rent cost: sum(f_jk * x_jk)
office_rent_cost = pulp.lpSum([x_jk[(loc, scale)] * f_jk[(loc, scale)] for loc, scale in f_jk])

# 2. Traveling cost: sum(h_i * d_ij * y_ij)
traveling_cost = pulp.lpSum([h_i[cust] * d_ij[cust][loc] * y_ij[(cust, loc)]
for cust in h_i for loc in d_ij[cust]])

# 3. Engineer hosting cost: sum(c_j * w_j)
engineer_hosting_cost = pulp.lpSum([c_j[loc] * w_j[loc] for loc in c_j])

# Total cost (objective function)
total_cost = office_rent_cost + traveling_cost + engineer_hosting_cost

# Add the objective function to the problem
problem += total_cost, "Minimize Total Cost"

Constraints and Rules

  1. Facility Scale Constraint (At Most One Scale)
  • This constraint ensures that at each location j, only one facility scale (Small, Medium, or Large) can be chosen. If no scale is chosen, it indicates that the facility is not built.
  • Purpose: This is an at most constraint that limits the number of scale levels selected per location. It helps in deciding whether to open a facility and, if so, at which scale.
Figure by Author: At most one scale constraint

2. Customer Service Constraint (Linking Constraint)

For each customer i, they can only be served by a facility at location j if that facility has been built. This is a linking constraint. It connects the customer assignment variable yijyij​ with the facility decision variable xjkxjk​. This constraint enforces that customers can only be assigned to locations where a facility is present.

Figure by Author: Customer Service Linking Constraint

3. Customer Assignment Constraint

Each customer i must be assigned to exactly one facility that is close enough according to the reachability parameter Aij.

Figure by Author: Customer Assignment Constraint

4. Facility Capacity Constraint

The number of engineers wj​ allocated to a facility at location jj must not exceed the facility’s capacity. This constraint limits the number of engineers at each facility based on the scale chosen for that facility. It prevents over-allocation of resources, ensuring each facility operates within its designed capacity.

Figure by Author: Facility Capacity Constraint

5. Service Requirement Constraint

The number of engineers allocated at location j, multiplied by the number of services they can complete in a year, must meet or exceed the total service demand assigned to that facility. This constraint ensures that the facility has enough engineers to handle the customer demand.

Figure by Author: Service Requirement Constraint

Code to implement constraints


# Constraint 1: At most one scale level for each location
for loc in c_j:
problem += pulp.lpSum([x_jk[(loc, scale)] for scale in ['Small', 'Medium', 'Large']]) <= 1, f"Max_One_Scale_{loc}"

# Constraint 2: Only a built facility may serve customers
for cust in h_i:
for loc in a_ij[cust]:
problem += y_ij[(cust, loc)] <= pulp.lpSum([x_jk[(loc, scale)] for scale in ['Small', 'Medium', 'Large']]), f"Serve_Only_Built_{cust}_{loc}"

# Constraint 3: Each customer must be served by one facility
for cust in h_i:
problem += pulp.lpSum([a_ij[cust][loc] * y_ij[(cust, loc)] for loc in a_ij[cust]]) == 1, f"Serve_Customer_{cust}"

# Ensures that the number of engineers allocated to a facility does not exceed its capacity based on the chosen scale.
for loc in m_jk:
problem += w_j[loc] <= pulp.lpSum([m_jk[loc][scale] * x_jk[(loc, scale)] for scale in ['Small', 'Medium', 'Large']]), f"Capacity_Constraint_{loc}"

# Ensures that the allocated engineers can fulfill the service demands assigned to a facility.
for loc in c_j:
problem += s * w_j[loc] >= pulp.lpSum([h_i[cust] * y_ij[(cust, loc)] for cust in h_i]), f"Service_Requirement_Constraint_{loc}"

# Solve the problem
problem.solve()

Output and Optimization Result

Figure by Author: Report after performing optimization

In the figure above, we see the outcome after running the optimization model. Each row represents a potential facility location, showing which ones end up operating (and at what scale), as well as how many engineers and customers are allocated there. For example, Boston is chosen at a Small scale with 10 engineers assigned, serving enough customers to deliver 1,900 services. In contrast, some locations, like New York and Philadelphia, are not selected to host any facilities — indicating that shutting them down or not expanding them further is more cost-effective. This reassignment of customers ensures demand is still met, but now with a lower total cost.

Moreover, the model’s result clarifies how many engineers the company should hire or retain for the coming year. Since we know exactly how many engineers each chosen location needs, the management can make informed hiring decisions, rather than guessing or overstaffing. Ultimately, this data-driven approach aligns facility choices, workforce planning, and customer assignments to minimize overall expenses while still ensuring that the company’s service obligations are fulfilled.

Code for report

import pandas as pd

# List to store results for each location
results = []

for loc in w_j:
engineers_allocated = int(w_j[loc].varValue) if w_j[loc].varValue is not None else 0
customers_assigned = sum([y_ij[(cust, loc)].varValue for cust in h_i if (cust, loc) in y_ij])
services_assigned = sum([h_i[cust] * y_ij[(cust, loc)].varValue for cust in h_i if (cust, loc) in y_ij])

chosen_scale = "None" # Default value
for scale in ['Small', 'Medium', 'Large']:
if (loc, scale) in x_jk and x_jk[(loc, scale)].varValue == 1:
chosen_scale = scale.capitalize()
break

# Append results to the list
results.append([loc, chosen_scale, engineers_allocated, int(customers_assigned), int(services_assigned)])

results_df = pd.DataFrame(results, columns=["Location", "Scale", "Engineers Allocated", "Customers Assigned", "Services Assigned"])

# Calculate totals and create a DataFrame for the totals row
totals = pd.DataFrame({
"Location": ["Total"],
"Scale": [f"Large {sum(results_df['Scale'] == 'Large')}, Small {sum(results_df['Scale'] == 'Small')}"],
"Engineers Allocated": [results_df["Engineers Allocated"].sum()],
"Customers Assigned": [results_df["Customers Assigned"].sum()],
"Services Assigned": [results_df["Services Assigned"].sum()]
})


results_df = pd.concat([results_df, totals], ignore_index=True)

# Print the results in table form
print(results_df)

Complete code on my repo: https://github.com/saifkheraj/d_s/blob/main/operationsresearch/facility_location.ipynb

References

Facility location problems – Mathematical Optimization: Solving Problems using Gurobi and Python

Todo Adapt figures, check maths Computational experiment comparing formulations Adapt kmedian – seems to be still…

scipbook.readthedocs.io

Facility Location Problem – Gurobi Optimization

Facility location problems can be commonly found in many industries, including logistics and telecommunications. In…

www.gurobi.com

Facility location problem – Wikipedia

From Wikipedia, the free encyclopedia A facility location problem is the problem of deciding where a given public…

en.wikipedia.org

Join thousands of data leaders on the AI newsletter. Join over 80,000 subscribers and keep up to date with the latest developments in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming a sponsor.

Published via Towards AI

Feedback ↓

Sign Up for the Course
`; } else { console.error('Element with id="subscribe" not found within the page with class "home".'); } } }); // Remove duplicate text from articles /* Backup: 09/11/24 function removeDuplicateText() { const elements = document.querySelectorAll('h1, h2, h3, h4, h5, strong'); // Select the desired elements const seenTexts = new Set(); // A set to keep track of seen texts const tagCounters = {}; // Object to track instances of each tag elements.forEach(el => { const tagName = el.tagName.toLowerCase(); // Get the tag name (e.g., 'h1', 'h2', etc.) // Initialize a counter for each tag if not already done if (!tagCounters[tagName]) { tagCounters[tagName] = 0; } // Only process the first 10 elements of each tag type if (tagCounters[tagName] >= 2) { return; // Skip if the number of elements exceeds 10 } const text = el.textContent.trim(); // Get the text content const words = text.split(/\s+/); // Split the text into words if (words.length >= 4) { // Ensure at least 4 words const significantPart = words.slice(0, 5).join(' '); // Get first 5 words for matching // Check if the text (not the tag) has been seen before if (seenTexts.has(significantPart)) { // console.log('Duplicate found, removing:', el); // Log duplicate el.remove(); // Remove duplicate element } else { seenTexts.add(significantPart); // Add the text to the set } } tagCounters[tagName]++; // Increment the counter for this tag }); } removeDuplicateText(); */ // Remove duplicate text from articles function removeDuplicateText() { const elements = document.querySelectorAll('h1, h2, h3, h4, h5, strong'); // Select the desired elements const seenTexts = new Set(); // A set to keep track of seen texts const tagCounters = {}; // Object to track instances of each tag // List of classes to be excluded const excludedClasses = ['medium-author', 'post-widget-title']; elements.forEach(el => { // Skip elements with any of the excluded classes if (excludedClasses.some(cls => el.classList.contains(cls))) { return; // Skip this element if it has any of the excluded classes } const tagName = el.tagName.toLowerCase(); // Get the tag name (e.g., 'h1', 'h2', etc.) // Initialize a counter for each tag if not already done if (!tagCounters[tagName]) { tagCounters[tagName] = 0; } // Only process the first 10 elements of each tag type if (tagCounters[tagName] >= 10) { return; // Skip if the number of elements exceeds 10 } const text = el.textContent.trim(); // Get the text content const words = text.split(/\s+/); // Split the text into words if (words.length >= 4) { // Ensure at least 4 words const significantPart = words.slice(0, 5).join(' '); // Get first 5 words for matching // Check if the text (not the tag) has been seen before if (seenTexts.has(significantPart)) { // console.log('Duplicate found, removing:', el); // Log duplicate el.remove(); // Remove duplicate element } else { seenTexts.add(significantPart); // Add the text to the set } } tagCounters[tagName]++; // Increment the counter for this tag }); } removeDuplicateText(); //Remove unnecessary text in blog excerpts document.querySelectorAll('.blog p').forEach(function(paragraph) { // Replace the unwanted text pattern for each paragraph paragraph.innerHTML = paragraph.innerHTML .replace(/Author\(s\): [\w\s]+ Originally published on Towards AI\.?/g, '') // Removes 'Author(s): XYZ Originally published on Towards AI' .replace(/This member-only story is on us\. Upgrade to access all of Medium\./g, ''); // Removes 'This member-only story...' }); //Load ionic icons and cache them if ('localStorage' in window && window['localStorage'] !== null) { const cssLink = 'https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css'; const storedCss = localStorage.getItem('ionicons'); if (storedCss) { loadCSS(storedCss); } else { fetch(cssLink).then(response => response.text()).then(css => { localStorage.setItem('ionicons', css); loadCSS(css); }); } } function loadCSS(css) { const style = document.createElement('style'); style.innerHTML = css; document.head.appendChild(style); } //Remove elements from imported content automatically function removeStrongFromHeadings() { const elements = document.querySelectorAll('h1, h2, h3, h4, h5, h6, span'); elements.forEach(el => { const strongTags = el.querySelectorAll('strong'); strongTags.forEach(strongTag => { while (strongTag.firstChild) { strongTag.parentNode.insertBefore(strongTag.firstChild, strongTag); } strongTag.remove(); }); }); } removeStrongFromHeadings(); "use strict"; window.onload = () => { /* //This is an object for each category of subjects and in that there are kewords and link to the keywods let keywordsAndLinks = { //you can add more categories and define their keywords and add a link ds: { keywords: [ //you can add more keywords here they are detected and replaced with achor tag automatically 'data science', 'Data science', 'Data Science', 'data Science', 'DATA SCIENCE', ], //we will replace the linktext with the keyword later on in the code //you can easily change links for each category here //(include class="ml-link" and linktext) link: 'linktext', }, ml: { keywords: [ //Add more keywords 'machine learning', 'Machine learning', 'Machine Learning', 'machine Learning', 'MACHINE LEARNING', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, ai: { keywords: [ 'artificial intelligence', 'Artificial intelligence', 'Artificial Intelligence', 'artificial Intelligence', 'ARTIFICIAL INTELLIGENCE', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, nl: { keywords: [ 'NLP', 'nlp', 'natural language processing', 'Natural Language Processing', 'NATURAL LANGUAGE PROCESSING', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, des: { keywords: [ 'data engineering services', 'Data Engineering Services', 'DATA ENGINEERING SERVICES', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, td: { keywords: [ 'training data', 'Training Data', 'training Data', 'TRAINING DATA', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, ias: { keywords: [ 'image annotation services', 'Image annotation services', 'image Annotation services', 'image annotation Services', 'Image Annotation Services', 'IMAGE ANNOTATION SERVICES', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, l: { keywords: [ 'labeling', 'labelling', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, pbp: { keywords: [ 'previous blog posts', 'previous blog post', 'latest', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, mlc: { keywords: [ 'machine learning course', 'machine learning class', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, }; //Articles to skip let articleIdsToSkip = ['post-2651', 'post-3414', 'post-3540']; //keyword with its related achortag is recieved here along with article id function searchAndReplace(keyword, anchorTag, articleId) { //selects the h3 h4 and p tags that are inside of the article let content = document.querySelector(`#${articleId} .entry-content`); //replaces the "linktext" in achor tag with the keyword that will be searched and replaced let newLink = anchorTag.replace('linktext', keyword); //regular expression to search keyword var re = new RegExp('(' + keyword + ')', 'g'); //this replaces the keywords in h3 h4 and p tags content with achor tag content.innerHTML = content.innerHTML.replace(re, newLink); } function articleFilter(keyword, anchorTag) { //gets all the articles var articles = document.querySelectorAll('article'); //if its zero or less then there are no articles if (articles.length > 0) { for (let x = 0; x < articles.length; x++) { //articles to skip is an array in which there are ids of articles which should not get effected //if the current article's id is also in that array then do not call search and replace with its data if (!articleIdsToSkip.includes(articles[x].id)) { //search and replace is called on articles which should get effected searchAndReplace(keyword, anchorTag, articles[x].id, key); } else { console.log( `Cannot replace the keywords in article with id ${articles[x].id}` ); } } } else { console.log('No articles found.'); } } let key; //not part of script, added for (key in keywordsAndLinks) { //key is the object in keywords and links object i.e ds, ml, ai for (let i = 0; i < keywordsAndLinks[key].keywords.length; i++) { //keywordsAndLinks[key].keywords is the array of keywords for key (ds, ml, ai) //keywordsAndLinks[key].keywords[i] is the keyword and keywordsAndLinks[key].link is the link //keyword and link is sent to searchreplace where it is then replaced using regular expression and replace function articleFilter( keywordsAndLinks[key].keywords[i], keywordsAndLinks[key].link ); } } function cleanLinks() { // (making smal functions is for DRY) this function gets the links and only keeps the first 2 and from the rest removes the anchor tag and replaces it with its text function removeLinks(links) { if (links.length > 1) { for (let i = 2; i < links.length; i++) { links[i].outerHTML = links[i].textContent; } } } //arrays which will contain all the achor tags found with the class (ds-link, ml-link, ailink) in each article inserted using search and replace let dslinks; let mllinks; let ailinks; let nllinks; let deslinks; let tdlinks; let iaslinks; let llinks; let pbplinks; let mlclinks; const content = document.querySelectorAll('article'); //all articles content.forEach((c) => { //to skip the articles with specific ids if (!articleIdsToSkip.includes(c.id)) { //getting all the anchor tags in each article one by one dslinks = document.querySelectorAll(`#${c.id} .entry-content a.ds-link`); mllinks = document.querySelectorAll(`#${c.id} .entry-content a.ml-link`); ailinks = document.querySelectorAll(`#${c.id} .entry-content a.ai-link`); nllinks = document.querySelectorAll(`#${c.id} .entry-content a.ntrl-link`); deslinks = document.querySelectorAll(`#${c.id} .entry-content a.des-link`); tdlinks = document.querySelectorAll(`#${c.id} .entry-content a.td-link`); iaslinks = document.querySelectorAll(`#${c.id} .entry-content a.ias-link`); mlclinks = document.querySelectorAll(`#${c.id} .entry-content a.mlc-link`); llinks = document.querySelectorAll(`#${c.id} .entry-content a.l-link`); pbplinks = document.querySelectorAll(`#${c.id} .entry-content a.pbp-link`); //sending the anchor tags list of each article one by one to remove extra anchor tags removeLinks(dslinks); removeLinks(mllinks); removeLinks(ailinks); removeLinks(nllinks); removeLinks(deslinks); removeLinks(tdlinks); removeLinks(iaslinks); removeLinks(mlclinks); removeLinks(llinks); removeLinks(pbplinks); } }); } //To remove extra achor tags of each category (ds, ml, ai) and only have 2 of each category per article cleanLinks(); */ //Recommended Articles var ctaLinks = [ /* ' ' + '

Subscribe to our AI newsletter!

' + */ '

Take our 85+ lesson From Beginner to Advanced LLM Developer Certification: From choosing a project to deploying a working product this is the most comprehensive and practical LLM course out there!

'+ '

Towards AI has published Building LLMs for Production—our 470+ page guide to mastering LLMs with practical projects and expert insights!

' + '
' + '' + '' + '

Note: Content contains the views of the contributing authors and not Towards AI.
Disclosure: This website may contain sponsored content and affiliate links.

' + 'Discover Your Dream AI Career at Towards AI Jobs' + '

Towards AI has built a jobs board tailored specifically to Machine Learning and Data Science Jobs and Skills. Our software searches for live AI jobs each hour, labels and categorises them and makes them easily searchable. Explore over 10,000 live jobs today with Towards AI Jobs!

' + '
' + '

🔥 Recommended Articles 🔥

' + 'Why Become an LLM Developer? Launching Towards AI’s New One-Stop Conversion Course'+ 'Testing Launchpad.sh: A Container-based GPU Cloud for Inference and Fine-tuning'+ 'The Top 13 AI-Powered CRM Platforms
' + 'Top 11 AI Call Center Software for 2024
' + 'Learn Prompting 101—Prompt Engineering Course
' + 'Explore Leading Cloud Providers for GPU-Powered LLM Training
' + 'Best AI Communities for Artificial Intelligence Enthusiasts
' + 'Best Workstations for Deep Learning
' + 'Best Laptops for Deep Learning
' + 'Best Machine Learning Books
' + 'Machine Learning Algorithms
' + 'Neural Networks Tutorial
' + 'Best Public Datasets for Machine Learning
' + 'Neural Network Types
' + 'NLP Tutorial
' + 'Best Data Science Books
' + 'Monte Carlo Simulation Tutorial
' + 'Recommender System Tutorial
' + 'Linear Algebra for Deep Learning Tutorial
' + 'Google Colab Introduction
' + 'Decision Trees in Machine Learning
' + 'Principal Component Analysis (PCA) Tutorial
' + 'Linear Regression from Zero to Hero
'+ '

', /* + '

Join thousands of data leaders on the AI newsletter. It’s free, we don’t spam, and we never share your email address. Keep up to date with the latest work in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming a sponsor.

',*/ ]; var replaceText = { '': '', '': '', '
': '
' + ctaLinks + '
', }; Object.keys(replaceText).forEach((txtorig) => { //txtorig is the key in replacetext object const txtnew = replaceText[txtorig]; //txtnew is the value of the key in replacetext object let entryFooter = document.querySelector('article .entry-footer'); if (document.querySelectorAll('.single-post').length > 0) { //console.log('Article found.'); const text = entryFooter.innerHTML; entryFooter.innerHTML = text.replace(txtorig, txtnew); } else { // console.log('Article not found.'); //removing comment 09/04/24 } }); var css = document.createElement('style'); css.type = 'text/css'; css.innerHTML = '.post-tags { display:none !important } .article-cta a { font-size: 18px; }'; document.body.appendChild(css); //Extra //This function adds some accessibility needs to the site. function addAlly() { // In this function JQuery is replaced with vanilla javascript functions const imgCont = document.querySelector('.uw-imgcont'); imgCont.setAttribute('aria-label', 'AI news, latest developments'); imgCont.title = 'AI news, latest developments'; imgCont.rel = 'noopener'; document.querySelector('.page-mobile-menu-logo a').title = 'Towards AI Home'; document.querySelector('a.social-link').rel = 'noopener'; document.querySelector('a.uw-text').rel = 'noopener'; document.querySelector('a.uw-w-branding').rel = 'noopener'; document.querySelector('.blog h2.heading').innerHTML = 'Publication'; const popupSearch = document.querySelector$('a.btn-open-popup-search'); popupSearch.setAttribute('role', 'button'); popupSearch.title = 'Search'; const searchClose = document.querySelector('a.popup-search-close'); searchClose.setAttribute('role', 'button'); searchClose.title = 'Close search page'; // document // .querySelector('a.btn-open-popup-search') // .setAttribute( // 'href', // 'https://medium.com/towards-artificial-intelligence/search' // ); } // Add external attributes to 302 sticky and editorial links function extLink() { // Sticky 302 links, this fuction opens the link we send to Medium on a new tab and adds a "noopener" rel to them var stickyLinks = document.querySelectorAll('.grid-item.sticky a'); for (var i = 0; i < stickyLinks.length; i++) { /* stickyLinks[i].setAttribute('target', '_blank'); stickyLinks[i].setAttribute('rel', 'noopener'); */ } // Editorial 302 links, same here var editLinks = document.querySelectorAll( '.grid-item.category-editorial a' ); for (var i = 0; i < editLinks.length; i++) { editLinks[i].setAttribute('target', '_blank'); editLinks[i].setAttribute('rel', 'noopener'); } } // Add current year to copyright notices document.getElementById( 'js-current-year' ).textContent = new Date().getFullYear(); // Call functions after page load extLink(); //addAlly(); setTimeout(function() { //addAlly(); //ideally we should only need to run it once ↑ }, 5000); }; function closeCookieDialog (){ document.getElementById("cookie-consent").style.display = "none"; return false; } setTimeout ( function () { closeCookieDialog(); }, 15000); console.log(`%c 🚀🚀🚀 ███ █████ ███████ █████████ ███████████ █████████████ ███████████████ ███████ ███████ ███████ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ Towards AI is looking for contributors! │ │ Join us in creating awesome AI content. │ │ Let's build the future of AI together → │ │ https://towardsai.net/contribute │ │ │ └───────────────────────────────────────────────────────────────────┘ `, `background: ; color: #00adff; font-size: large`); //Remove latest category across site document.querySelectorAll('a[rel="category tag"]').forEach(function(el) { if (el.textContent.trim() === 'Latest') { // Remove the two consecutive spaces (  ) if (el.nextSibling && el.nextSibling.nodeValue.includes('\u00A0\u00A0')) { el.nextSibling.nodeValue = ''; // Remove the spaces } el.style.display = 'none'; // Hide the element } }); // Add cross-domain measurement, anonymize IPs 'use strict'; //var ga = gtag; ga('config', 'G-9D3HKKFV1Q', 'auto', { /*'allowLinker': true,*/ 'anonymize_ip': true/*, 'linker': { 'domains': [ 'medium.com/towards-artificial-intelligence', 'datasets.towardsai.net', 'rss.towardsai.net', 'feed.towardsai.net', 'contribute.towardsai.net', 'members.towardsai.net', 'pub.towardsai.net', 'news.towardsai.net' ] } */ }); ga('send', 'pageview'); -->