{"id":2205,"date":"2017-06-07T12:02:23","date_gmt":"2017-06-07T16:02:23","guid":{"rendered":"https:\/\/www.danielpradilla.info\/blog\/?p=2205\/"},"modified":"2018-12-03T13:39:09","modified_gmt":"2018-12-03T13:39:09","slug":"linear-optimization-with-or-tools","status":"publish","type":"post","link":"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools\/","title":{"rendered":"Linear Optimization with or-tools"},"content":{"rendered":"<p>&nbsp;<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"2207\" data-permalink=\"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools\/grocery_bag_brown_bag\/\" data-orig-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/06\/grocery_bag_brown_bag.jpg\" data-orig-size=\"739,510\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"grocery_bag_brown_bag\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/06\/grocery_bag_brown_bag.jpg\" class=\"aligncenter size-full wp-image-2207\" src=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/06\/grocery_bag_brown_bag.jpg\" alt=\"\" width=\"739\" height=\"510\" srcset=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/06\/grocery_bag_brown_bag.jpg 739w, https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/06\/grocery_bag_brown_bag-300x207.jpg 300w\" sizes=\"auto, (max-width: 739px) 100vw, 739px\" \/><\/p>\n<h1>Getting started<\/h1>\n<p>Over the last couple of months I&#8217;ve been getting my feet wet with linear programming and mathematical optimisation. I got a sense of how it all worked from <a href=\"https:\/\/www.coursera.org\/learn\/discrete-optimization\/home\/welcome\" target=\"_blank\" rel=\"noopener noreferrer\">this Discrete Optimisation course in Coursera<\/a> and googling around I discovered that there are a ton of tools out there to help you solve optimisation problems. Makes sense, why would you want to implement a solving algorithm from scratch when some of the best minds in the history of mankind have already given it a shot?<\/p>\n<p>Solvers, as these tools are often called, can reach the hundreds of thousands of dollars and they are worth every penny! But since I wanted to play with one without forking up the dough, I narrowed my search down to open-source options.<\/p>\n<p>Hans Mittelmann from the Arizona State University performs regular automated benchmarks on different mathematical optimisation tools. He publishes the benchmarks at <a href=\"http:\/\/plato.asu.edu\/bench.html\" target=\"_blank\" rel=\"noopener noreferrer\">http:\/\/plato.asu.edu\/bench.html<\/a>. Following what I read in the results, I picked <a href=\"https:\/\/developers.google.com\/optimization\/\" target=\"_blank\" rel=\"noopener noreferrer\">Google Optimization Tools (OR-Tools)<\/a> because it performed fairly well and well, because if you&#8217;re looking for a sinister tool to model and solve hard problems in the human world, you can rarely go wrong with Google.<\/p>\n<p>GLOP has C++ and Python APIs. I&#8217;m better at Python and I expected to quickly put together a web front-end for this, so I picked the latter one.<\/p>\n<h2>TL;DR. Just gimme the code<\/h2>\n<p><a href=\"https:\/\/github.com\/danielpradilla\/or-tools-playground\/blob\/master\/www\/interview_grocery_startup\/interview_grocery_startup.py\">You can get the full code here<\/a>.<\/p>\n<p>&nbsp;<\/p>\n<h2>The Problem<\/h2>\n<p>I picked a variation of the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Knapsack_problem\">knapsack problem<\/a>: a grocery-shopping example in which you try to maximise the number of calories you can buy with a limited budget. I got the problem from this blog post:<\/p>\n<p><a href=\"http:\/\/www.jasq.org\/just-another-scala-quant\/new-agey-interviews-at-the-grocery-startup\">http:\/\/www.jasq.org\/just-another-scala-quant\/new-agey-interviews-at-the-grocery-startup<\/a><\/p>\n<p>Basically: You walk into a grocery store with a grocery bag and some cash, to buy groceries for a week. You need to follow these rules:<\/p>\n<p>1. Your bag can hold ten pounds.<br \/>\n2. You have $100<br \/>\n3. You need about 2000 calories a day, so a weekly shopping trip is about 14,000 calories.<br \/>\n4. You must purchase at least 4 ounces of each grocery item.<\/p>\n<p>These are the groceries you can by and their price per pound:<\/p>\n<pre>Ham:     650 cals,\u00a0 $4\r\nLettuce:  70 cals,\u00a0 $1.5\r\nCheese: 1670 cals,\u00a0 $5\r\nTuna:    830 cals,\u00a0$20\r\nBread:  1300 cals,\u00a0 $1.20<\/pre>\n<p>&nbsp;<\/p>\n<h2>Installing OR-Tools<\/h2>\n<p>Follow the instructions at\u00a0<a href=\"https:\/\/developers.google.com\/optimization\/introduction\/installing\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/developers.google.com\/optimization\/introduction\/installing<\/a>. You will need Python and <a href=\"https:\/\/pypi.python.org\/pypi\/setuptools\">Python setuptools<\/a> installed in your machine.<\/p>\n<p>&nbsp;<\/p>\n<h2>Coding the problem<\/h2>\n<p>We can split the coding of the problem into 6 elements:<\/p>\n<h3>1. Identify the problem<\/h3>\n<p>We tell or-tools that we are attempting to solve a linear programming problem. We create a solver variable that is going to contain all the necessary items to solve the problem.<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nfrom ortools.linear_solver import pywraplp\r\nsolver = pywraplp.Solver('SolveSimpleSystem',pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)\r\n<\/pre>\n<p>&nbsp;<\/p>\n<h3>2. Ingest the input<\/h3>\n<p>We are going to send our table of possible groceries, calories and prices as a nested list:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nfood = &#x5B;&#x5B;'ham',650, 4],\r\n &#x5B;'lettuce',70,1.5],\r\n &#x5B;'cheese',1670,5],\r\n &#x5B;'tuna',830,20],\r\n &#x5B;'bread',1300,1.20]]\r\n<\/pre>\n<p>&nbsp;<\/p>\n<h3>3. Configure the decision variables<\/h3>\n<p>We need 5 decision variables which contain how many pounds of each product you are going to buy. Instead of creating 5 variables, we can create a list of size 5 (the size of the food list). Each item in the list will contain a decision variable.<\/p>\n<p>Each decision variable will be created with a call to the NumVar method of the solver variable, passing the minimum amount of groceries we can buy, the maximum (infinity), and a unique name for the variable (contained in the previously-defined food list).<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\n    #food is a list of groceries, calories and prices\r\n    variable_list = &#x5B;&#x5B;]] * len(food)\r\n    for i in range(0, len(food)):\r\n        #you must buy at least minShop of each\r\n        variable_list&#x5B;i] = solver.NumVar(minShop, solver.infinity(), str(food&#x5B;i]&#x5B;0]))\r\n<\/pre>\n<p>They <em>pythonic<\/em> way of writing\u00a0that loop is using a <a href=\"https:\/\/docs.python.org\/2\/tutorial\/datastructures.html#list-comprehensions\">list comprehension<\/a>. However I&#8217;m using the loop for readability.<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\"> \r\n    #same thing but with comprehension\r\n    variable_list=&#x5B;solver.NumVar(minShop, solver.infinity(), str(food&#x5B;i]&#x5B;0])) for i in range(0, len(food))]\r\n<\/pre>\n<p>&nbsp;<\/p>\n<h3>4. Configure the constraints<\/h3>\n<p>This is where most of the magic happens. We will create one constraint per &#8220;rule&#8221; specified in the problem description.<\/p>\n<p>In linear programming each constraint is specified in terms of addition of the decision variables:<\/p>\n<pre>lower bound &lt;= var1+var2+var3 &lt;=upper bound<\/pre>\n<p>In the knapsack problem, the conversion is pretty straightforward. In some other cases, you have to re-think and re-model your problem in these terms.<\/p>\n<p>We will create a list of 3 constraints, calling Constraint(lower bound, upper bound) for each one, and then walk the variables list and call SetCoefficient for each of the variables.<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\"> \r\n    #Define the constraints    \r\n    constraint_list=&#x5B;]\r\n    #Constraint 1: totalWeight&lt;maxWeight\r\n    #ham + lettuce + cheese + tuna + bread &lt;= maxWeight\r\n    constraint_list.append(solver.Constraint(0, maxWeight))\r\n    for i in range(0, len(food)):\r\n        constraint_list&#x5B;0].SetCoefficient(variable_list&#x5B;i],1)\r\n\r\n    #Constraint 2: totalPrice&lt;=maxCost \r\n    constraint_list.append(solver.Constraint(0, maxCost)) \r\n    for i in range(0, len(food)): \r\n        constraint_list&#x5B;1].SetCoefficient(variable_list&#x5B;i],food&#x5B;i]&#x5B;2]) \r\n\r\n    #Constraint 3: totalCalories&gt;=minCals\r\n    constraint_list.append(solver.Constraint(minCals, minCals + 100))\r\n    for i in range(0, len(food)):\r\n        constraint_list&#x5B;2].SetCoefficient(variable_list&#x5B;i],food&#x5B;i]&#x5B;1])\r\n<\/pre>\n<p>Note that the 4th rule of the problem, &#8220;You must purchase at least 4 ounces of each grocery item,&#8221; is already coded in the variables definition.<\/p>\n<p>&nbsp;<\/p>\n<h3>5. Configure the objective function<\/h3>\n<p>Similar to the constraint definition, the goal function is specified in terms of addition of the decision variables:<\/p>\n<pre>goal = Maximize\/Minimize (var1+var2+var3)<\/pre>\n<p>If we wish to minimize cost, we walk our variable list, get the price from the food list, and set the objective.<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\"> \r\n        for i in range(0, len(variable_list&#x5B;)):\r\n            objective.SetCoefficient(variable_list&#x5B;i], food&#x5B;i]&#x5B;2])\r\n        objective.SetMinimization()\r\n<\/pre>\n<p>Say we wanted to maximize calories intake. We would do the same, but taking the calories value from the food list, and setting a maximization goal<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\n# Define our objective: maximizing calories\r\nfor i in range(0, len(food)):\r\n    objective.SetCoefficient(variable_list&#x5B;i], food&#x5B;i]&#x5B;1])\r\nobjective.SetMaximization()\r\n<\/pre>\n<p>&nbsp;<\/p>\n<h3>6. Solve!<\/h3>\n<p>After all these configuration steps, we just call the solve method against the solver variable and print out a solution if we find it.<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\"> \r\n    result_status = solve(solver)\r\n\r\n    if result_status == solver.OPTIMAL:\r\n        print('Successful solve.')\r\n        # The problem has an optimal solution.\r\n        print(('Problem solved in %f milliseconds' % solver.wall_time()))\r\n        # The objective value of the solution.\r\n        print(('Optimal objective value = %f' % solver.Objective().Value()))\r\n        # The value of each variable in the solution.\r\n        var_sum=0\r\n        for variable in variable_list:\r\n            print(('%s = %f' % (variable.name(), variable.solution_value())))\r\n            var_sum+=variable.solution_value()\r\n        print(('Variable sum = %f' % var_sum));\r\n\r\n        print('Advanced usage:')\r\n        print(('Problem solved in %d iterations' % solver.iterations()))\r\n\r\n        for variable in variable_list:\r\n            print(('%s: reduced cost = %f' % (variable.name(), variable.reduced_cost())))\r\n        \r\n        activities = solver.ComputeConstraintActivities()\r\n        for i, constraint in enumerate(constraint_list):\r\n            print(('constraint %d: dual value = %f\\n'\r\n              '               activity = %f' %\r\n              (i, constraint.dual_value(), activities&#x5B;constraint.index()])))\r\n\r\n    elif result_status == solver.INFEASIBLE:\r\n        print('No solution found.')\r\n    elif result_status == solver.POSSIBLE_OVERFLOW:\r\n        print('Some inputs are too large and may cause an integer overflow.')\r\n\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><a href=\"https:\/\/github.com\/danielpradilla\/or-tools-playground\/blob\/master\/www\/interview_grocery_startup\/interview_grocery_startup.py\">You can get the full code here<\/a>.<\/p>\n<p>&nbsp;<\/p>\n<h2>Next steps<\/h2>\n<p>We got a solution, but you need to know a little bit of python to run this program, change its inputs or read the solution. Wouldn&#8217;t it be nice to have some sort of user-friendly UI? This will be the subject of future posts.<\/p>\n<p><a href=\"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools-building-a-web-front-end-with-falcon-and-gunicorn\/\">Building a web front-end with falcon and gunicorn<\/a><\/p>\n<p>Containerizing the solution with docker<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>&nbsp; Getting started Over the last couple of months I&#8217;ve been getting my feet wet with linear programming and mathematical optimisation. I got a sense of how it all worked from this Discrete Optimisation course in Coursera and googling around I discovered that there are a ton of tools out there to help you solve&hellip; <a class=\"more-link\" href=\"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools\/\">Continue reading <span class=\"screen-reader-text\">Linear Optimization with or-tools<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[174,331],"tags":[340,337,338,339,341],"class_list":["post-2205","post","type-post","status-publish","format-standard","hentry","category-bestof","category-software-development-en-en","tag-google","tag-linear-programming","tag-optimization","tag-or-tools","tag-python","entry"],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1tlzy-zz","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":2212,"url":"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools-building-a-web-front-end-with-falcon-and-gunicorn\/","url_meta":{"origin":2205,"position":0},"title":"Linear Optimization with or-tools \u00e2\u20ac\u201d building a web front-end with falcon and gunicorn","author":"Daniel Pradilla","date":"14\/11\/2017","format":false,"excerpt":"In a previous post, I put together a script for solving a linear optimisation problem using Google's OR-tools. This python script is callable from the command line and you kinda need to know what you are doing and how to organize the parameters. So, in order to address this difficulty,\u2026","rel":"","context":"In &quot;Software Dev.&quot;","block_context":{"text":"Software Dev.","link":"https:\/\/www.danielpradilla.info\/blog\/category\/software-development-en-en\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceryshopping.gif?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceryshopping.gif?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceryshopping.gif?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceryshopping.gif?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":2229,"url":"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools-containerizing-a-gunicorn-web-application\/","url_meta":{"origin":2205,"position":1},"title":"Linear optimization with or-tools: containerizing a gunicorn web application","author":"Daniel Pradilla","date":"15\/05\/2018","format":false,"excerpt":"Previously, we left our app working with our local python+gunicorn+nginx installation. In order to get there we had to do quite a bit of configuration and if we wanted to deploy this in a server or send it to a friend, we would have to go through a very error-prone\u2026","rel":"","context":"In &quot;Software Dev.&quot;","block_context":{"text":"Software Dev.","link":"https:\/\/www.danielpradilla.info\/blog\/category\/software-development-en-en\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2018\/05\/docker.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2018\/05\/docker.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2018\/05\/docker.jpg?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":2062,"url":"https:\/\/www.danielpradilla.info\/blog\/making-sense-of-numbers-while-browsing-the-web\/","url_meta":{"origin":2205,"position":2},"title":"Making sense of numbers while browsing the web","author":"Daniel Pradilla","date":"21\/01\/2014","format":false,"excerpt":"https:\/\/www.youtube.com\/watch?v=4xlSErmEmso Almost every time we read an article, we are faced with numbers. Good writers try to pre-digest those figures for you with analogies. But most of the times, we are left alone to decipher what the numbers mean. The thing is that we rarely interrupt our reading to stop\u2026","rel":"","context":"In &quot;Software Dev.&quot;","block_context":{"text":"Software Dev.","link":"https:\/\/www.danielpradilla.info\/blog\/category\/software-development-en-en\/"},"img":{"alt_text":"Screen Shot 2014-01-19 at 11.01.29 AM","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2014\/01\/Screen-Shot-2014-01-19-at-11.01.29-AM.png?resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2014\/01\/Screen-Shot-2014-01-19-at-11.01.29-AM.png?resize=350%2C200 1x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2014\/01\/Screen-Shot-2014-01-19-at-11.01.29-AM.png?resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2014\/01\/Screen-Shot-2014-01-19-at-11.01.29-AM.png?resize=700%2C400 2x"},"classes":[]},{"id":1987,"url":"https:\/\/www.danielpradilla.info\/blog\/short-guide-to-pentaho-data-integration\/","url_meta":{"origin":2205,"position":3},"title":"A short and sweet introduction to Pentaho Data Integration","author":"Daniel Pradilla","date":"16\/08\/2013","format":false,"excerpt":"Whenever I have to create or maintain a Pentaho Data Integration scheduled job I have to go back to the Pentaho wiki or google for use cases. Although I should, I never remember the specifics and going through 2007 forum posts and outdated documentation is always a pain. Recently, I\u2026","rel":"","context":"In &quot;Project Mgmt.&quot;","block_context":{"text":"Project Mgmt.","link":"https:\/\/www.danielpradilla.info\/blog\/category\/projectmanagement-en\/"},"img":{"alt_text":"6906OT_Instant Pentaho Data Integration Kitchen","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2013\/08\/6906OT_Instant-Pentaho-Data-Integration-Kitchen.jpg?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":1914,"url":"https:\/\/www.danielpradilla.info\/blog\/4-ways-of-getting-the-most-out-of-your-dropbox\/","url_meta":{"origin":2205,"position":4},"title":"4 ways of getting the most out of your dropbox","author":"Daniel Pradilla","date":"04\/02\/2013","format":false,"excerpt":"Dropbox is one of my essential tools.\u00a0If you already have it, I've got 4 ways of using it that you may not know about: \u00a0 1.\u00a0Upload files via email Send To Dropbox\u00a0is a service that assigns you an email address linked to your Dropbox. Everything that you send or forward\u2026","rel":"","context":"In &quot;Project Mgmt.&quot;","block_context":{"text":"Project Mgmt.","link":"https:\/\/www.danielpradilla.info\/blog\/category\/projectmanagement-en\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2012\/01\/dropbox_logo.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":2074,"url":"https:\/\/www.danielpradilla.info\/blog\/nate-silver-and-the-age-of-data-journalism\/","url_meta":{"origin":2205,"position":5},"title":"Nate Silver And The Age of Data Journalism","author":"Daniel Pradilla","date":"24\/03\/2014","format":false,"excerpt":"A few days ago, the new version of Nate Silver's FiveThirtyEight went live, backed by ESPN. According to Silver's observations, explained in his site's manifesto, the market is ripe for a data-oriented journalism. I totally agree. A day doesn't go by in which I hear or read an argument that\u2026","rel":"","context":"In &quot;Lifestyle&quot;","block_context":{"text":"Lifestyle","link":"https:\/\/www.danielpradilla.info\/blog\/category\/lifestyle\/"},"img":{"alt_text":"538_intro4","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2014\/03\/538_intro4.png?resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2014\/03\/538_intro4.png?resize=350%2C200 1x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2014\/03\/538_intro4.png?resize=525%2C300 1.5x"},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/posts\/2205","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/comments?post=2205"}],"version-history":[{"count":0,"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/posts\/2205\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/media?parent=2205"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/categories?post=2205"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/tags?post=2205"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}