YouTube Videos

A Simple Neural Network
KotlinConf 2018 - Mathematical Modeling
Creating a Sudoku Solver from Scratch
Traveling Salesman Problem
Text Categorization w/ Naive Bayes
Monty Hall Problem
Solving World's Hardest Sudoku

Saturday, January 13, 2018

Kotlin and Linear Programming Part II - Linear Optimization

Untitled Document.md

In my previous article in this series, I introduced using linear programming with Kotlin. Again, linear programming broadly encompasses linear, integer, and binary programming all of which can be helpful for optimization problems. It was probably unconventional I started with binary programming, but it arguably makes linear programming useful beyond simple mixing problems. For instance, binary programming enables tackling complex scheduling problems which I will revisit again in my next post. But for now, let’s get pure linear programming out of the way. Then I will get to the exciting stuff.

Linear Programming and Optimization

Linear programming, in its purest form, optimizes continuous variables. Continuous means a variable can optimize to a decimal precision rather than a whole number. Unlike integer programming where a variable evaluates to a whole integer like 2, linear programming can optimize it to 2.4, 2.4012 or even 2.04000125. Surprisingly, this is much easier for a machine to solve than integer or binary programming. The machine can gracefully solve variables that truly are linear, rather than rely on brute-force approximations that integer programming requires. When you mix linear and integer programming together, it is called mixed programming.

One thing we really did not touch on in the last article is optimization. Our abstract binary problem was not given a goal other than find a solution that was “feasible” and satisfied the constraints. But in optimization, many solutions can exist. For example, an optimizer can produce many possible manufacturing plans (produce this many items of Product X, and this many Product Y), but it is likely we want the one that is the least costly, or most profitable. Therefore we can provide a “goal” function that is used to choose the best solution. This function will mathematically manipulate the variables in a way that expresses cost, revenue, machine/staff utilization, etc and the algorithm will minimize or maximize that function. You can even have multiple goals put into one function, with each one weighted based on their importance.

The Driver Problem

There are a lot of linear problem examples out there that deal with blending, from manufacturing cars to making sausage. But I am interested in scheduling at the moment so let’s put a linear twist on that, even if it conventionally is an integer problem.

At KotlinConf, I presented a driver scheduling example which garnered interest. This is a linear problem with continuous and binary variables, therefore making it a mixed problem. Driver shift start and end variables are allowed to move freely and continuously throughout the day, and the shift must be 4-6 hours in length. This continuous definition of schedule shifts is not likely how schedules are built in real life (they are usually discrete and integer-based), but it is an interesting application nonetheless. Let’s take a look at it:

You have three drivers who charge the following rates:

Driver 1: $10 / hr
Driver 2: $12 / hr
Driver 3: $15 / hr

From 6:00 to 22:00, schedule one driver at a time to provide coverage, and minimize cost.

Each driver must work 4-6 hours a day. Driver 2 cannot work after 11:00.

We could expand the scope of this problem significantly, scheduling an entire week and putting additional constraints on that scope (e.g. a worker cannot be scheduled more than 30 hours/week). But let’s keep the scope of this problem limited to one day for now, and save multi-day scheduling for the next article.

Structuring the Problem

Let’s start by saving the operating day and allowable shift size as Kotlin ranges. If we represent the day as 24 hours, the operating day will be 6:00 to 22:00, or 6..22 as a Kotlin IntRange. The allowableShiftSize can only be 4 to 6 hours, which would be an IntRange of 4..6.

val operatingDay = 6..22
val allowableShiftSize = 4..6

Declare an instance of an ExpressionBasedModel. This is the entity that we will input constraint functions into. Let’s also define an improvised Kotlin DSL to streamline ojAlgo. These variable() and addExpression() functions will automatically assign a name to each variable and function, which personally I find tedious to do. Let’s also calculate the operatingDayLength and save it to a constant for convenience.

import org.ojalgo.optimisation.ExpressionsBasedModel
import org.ojalgo.optimisation.Variable
import java.util.concurrent.atomic.AtomicInteger

// declare ojAlgo Model
val model = ExpressionsBasedModel()

// custom DSL for  expression inputs, eliminate naming and adding
val funcId = AtomicInteger(0)
val variableId = AtomicInteger(0)
fun variable() = Variable(variableId.incrementAndGet().toString().let { "Variable$it" }).apply(model::addVariable)
fun addExpression() = funcId.incrementAndGet().let { "Func$it"}.let { model.addExpression(it) }


// constants
val operatingDayLength = operatingDay.endInclusive - operatingDay.start

Now let’s get to the core of the problem.

data class Driver(val driverNumber: Int,
                  val rate: Double,
                  val availability: IntRange? = null) {

    val shiftStart = variable().lower(6).upper(22)
    val shiftEnd = variable().lower(6).upper(22)

    fun addToModel() {
        // expression inputs go here
    }
}

As shown above, create a Driver class with two ojAlgo variables, which will contain the shiftStart and shiftEnd values after the model is optimized. Note these two variables are bound to a lower() of 6 and an upper() of 22 which is our operating day. Also declare a driverNumber which will simply act as an ID, the rate which will be type Double, and a nullable IntRange called availability for drivers that can work only certain hours. Finally, put an empty addToModel() function which is where we will write code that “puts” the driver mathematically into the ojAlgo model. We will get to this in a moment.

We can then hold a list of Drivers. As a placeholder, we will have a main() function that calls a buildModel() function, then minimize() the cost, and print the results.


// declare drivers
val drivers = listOf(
        Driver(driverNumber = 1, rate = 10.0),
        Driver(driverNumber = 2, rate = 12.0, availability = 6..11),
        Driver(driverNumber = 3, rate = 14.0)
)


// parameters
val operatingDay = 6..22
val allowableShiftSize = 4..6

fun main(args: Array<String>) {

    buildModel()

    model.minimise().run(::println)

    // see variables for each driver
    drivers.forEach {
        println("Driver ${it.driverNumber}: ${it.shiftStart.value.toInt()}-${it.shiftEnd.value.toInt()}")
    }
}

fun buildModel() {

}

Implementing the Math

Next we need to populate the buildModel() function with our mathematical constraints. Some of those constraints will be specific to each Driver, in which case those will be in the Driver’s addToModel() function. Other constraints will apply to all the drivers.

Day Coverage

We have already constrained the shiftStart and shiftEnd variables to the operating day, so we are good there (using the lower() and upper() functions). We still need a constraint that ensures our entire 16-hour operating day has been covered. This is pretty easy to express mathematically. The sum of differences between each shiftEnd and shiftStart for each driver must sum up to the operating day’s length:

Therefore in our buildModel() function we can call addExpression(), level() it to the operatingDayLength (which should be 16), and use a handy Kotlin apply() closure to loop through each driver. We add each driver’s shiftStart and shiftEnd variables to the expression with the appropriate 1 or -1 multiplier to add or subtract them.


fun buildModel() {

    //ensure coverage of entire day
    model.addExpression()
            .level(operatingDayLength)
            .apply {
                drivers.forEach {
                    set(it.shiftEnd, 1)
                    set(it.shiftStart, -1)
                }
            }
}

Cost Objective

Before we create expressions in context with each individal Driver, let’s define our objective(). We want to minimize cost, but for now we just need an expression that calculates the cost which we call the minimise() function against. This expression will be similar to the day coverage one, but we multiply each difference by the driver’s rate. This will yield the total cost.

Then we express this in the buildModel() function in a similar way as the day coverage, but when we call addExpression() and give it a weight(1) of 1. This will make the expression a goal driver rather than a constraint. Note we can actually provide multiple objectives and give each one a weight based on their importance, but we will stick with 1 for now.

fun buildModel() {

    //ensure coverage of entire day
    model.addExpression()
            .level(operatingDayLength)
            .apply {
                drivers.forEach {
                    set(it.shiftEnd, 1)
                    set(it.shiftStart, -1)
                }
            }

    // set objective
    model.expression().apply {
        weight(1)
        drivers.forEach {
            set(it.shiftEnd, it.rate)
            set(it.shiftStart, -1 * it.rate)
        }
    }

    // driver-specific expressions
    drivers.forEach { it.addToModel() }
}

Let’s also add a call to each driver’s addToModel() function which we will implment next.

Shift Length

A driver must work a minimum of 4 hours but a maximum of 6 hours. This one is pretty simple. We just express the difference of the shiftEnd and shiftStart as an inequality.

In the Driver’s addToModel() function, we can call addExpression(), constrain its lower() and upper() bounds to the allowable shift size range. Then we set the shiftStart and shiftEnd variables, multiplying the shiftStart by -1 so it is subtracted rather than added.

data class Driver(val driverNumber: Int,
                  val rate: Double,
                  val availability: IntRange? = null) {

    val shiftStart = variable().lower(6).upper(22)
    val shiftEnd = variable().lower(6).upper(22)

    fun addToModel() {

        //constrain shift length
        model.addExpression()
                .lower(allowableShiftSize.start)
                .upper(allowableShiftSize.endInclusive)
                .set(shiftEnd, 1)
                .set(shiftStart, -1)

    }
}

Shift Restrictions

Driver 2 is the only driver that restricted their shift to something less than the operating day. He wants to only work within the 6 to 11 range. This constraint is expressed as two very simple inequalities:

If a Driver indeed has provided a specific availability that is not null, we can take that range and use it to build those constraints as shown below:

data class Driver ... {

    fun addToModel() {

        //constrain shift length
        model.addExpression()
                .lower(allowableShiftSize.start)
                .upper(allowableShiftSize.endInclusive)
                .set(shiftEnd, 1)
                .set(shiftStart, -1)

        //add specific driver availability
        availability?.let {
            model.addExpression()
                    .lower(it.start)
                    .upper(it.endInclusive)
                    .set(shiftStart, 1)

            model.addExpression()
                    .lower(it.start)
                    .upper(it.endInclusive)
                    .set(shiftEnd, 1)
        }
    }
}

Preventing Shift Overlap

Okay, everything else up to this point was fairly easy, right? Now we come to the hard part and introduce some binary variables to prevent shift overlap. If you run with just these constraints we put in so far, we are going to have drivers overlapping their shifts! We need to prevent this and unfortunately, we have to think very abstractly to do it.

To prevent overlaps, each driver needs to have a mathematical relationship to the other drivers. Let’s focus on Driver 1 and Driver 2. The shiftStart of Driver 1 may be after the shiftEnd of Driver 2. But the shiftStart of Driver 2 could also be after the shiftEnd of Driver 1. Both cases are visually shown below:

  S2________E2          S1__________E1

  S1________E1          S2__________E2

Both are valid cases, and we need either to be true as a constraint to prevent overlap. But how do we support both cases in our mathematical model, which expects all constraints to be satisfied in order to yield a solution? You logically cannot declare both cases must be true, right? It seems like a paradox. We need to an “OR” rather than an “AND”.

So how do we mathematically express that “OR”? There is a way! For a given relationship between a driver i and another driver j, you can share a boolean variable between them that must be 1 or 0. Here is a modification to our constraints that effectively achieves the “OR”:

Well, that jumped up a notch. The M is going to be the length of the planning window, which is 16 hours. We just need M to be a very large number, so we could make it a 1000 but the window size should be fine.

The binary variable is represented by a delta, and it can only be optimized to 1 or 0. Notice the subtle math this achieves and allows both cases of Si coming before Ej, or Sj coming before Ei, but demands at least one of them must be true. M is a large enough number (the planning window) to throw the inequality far out enough that it allows the “false” case to now be true. It effectively nullifies. Thus, this achieves our much-needed “OR” operation for our constraint.

If your head is spinning pull out a pencil and paper, throw a few values at the expression, and you’ll see what I mean.

Here is how we can implement this for our Driver instance. It must establish this constraint with each other Driver. Note that ojAlgo needs all variables on one side of the equation and distributed, so we may have to do a little bit of basic algebra to move our variables around like this:

data class Driver ... {

    fun addToModel() {

        //constrain shift length
        model.addExpression()
                .lower(allowableShiftSize.start)
                .upper(allowableShiftSize.endInclusive)
                .set(shiftEnd, 1)
                .set(shiftStart, -1)

        //add specific driver availability
        availability?.let {
            model.addExpression()
                    .lower(it.start)
                    .upper(it.endInclusive)
                    .set(shiftStart, 1)

            model.addExpression()
                    .lower(it.start)
                    .upper(it.endInclusive)
                    .set(shiftEnd, 1)
        }

        //prevent shift overlap
       drivers.asSequence()
               .filter { it != this }
               .forEach { otherDriver ->

                   val occupied = variable().binary()

                   model.addExpression()
                           .upper(0)
                           .set(otherDriver.shiftEnd, 1)
                           .set(occupied, operatingDayLength * - 1)
                           .set(shiftStart, -1)

                   model.addExpression()
                           .upper(operatingDayLength)
                           .set(shiftEnd, 1)
                           .set(occupied, operatingDayLength)
                           .set(otherDriver.shiftStart, -1)
               }
    }
}

And there you have it. If you find this is dizzying to grasp, spend some time with pencil and paper, visualizing how this equation works and throw different scenarios and variable values at it. I have found visualizing the problem is the most effective way to make it intuitive.

Running the Program

Finally, go back to your main() function and call the minimise() function, and print the results of the variables like this:

fun main(args: Array<String>) {

    buildModel()

    model.minimise().run(::println)

    // see variables for each driver
    drivers.forEach {
        println("Driver ${it.driverNumber}: ${it.shiftStart.value.toInt()}-${it.shiftEnd.value.toInt()}")
    }
}

You should get the following output, with these shifts that will minimize the cost according to your constraints.

OPTIMAL 190.0 @ [11.0, 17.0, 6.0, 11.0, 17.0, 22.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0]
Driver 1: 11-17
Driver 2: 6-11
Driver 3: 17-22

Hopefully you found this somewhat interesting and exciting, and this topic of linear/integer programming is giving you new ways to look at optimization problems. In the next article, I will cover an ambitious scheduling example that is more real-life, and schedules classrooms and classes across an entire week.

Here is the entire code example. You can also view it on GitHub here.

import org.ojalgo.optimisation.ExpressionsBasedModel
import org.ojalgo.optimisation.Variable
import java.util.concurrent.atomic.AtomicInteger

// declare ojAlgo Model
val model = ExpressionsBasedModel()

// custom DSL for model expression inputs, eliminate naming and adding
val funcId = AtomicInteger(0)
val variableId = AtomicInteger(0)
fun variable() = Variable(variableId.incrementAndGet().toString().let { "Variable$it" }).apply(model::addVariable)
fun ExpressionsBasedModel.addExpression() = funcId.incrementAndGet().let { "Func$it"}.let { addExpression(it) }

// parameters
val operatingDay = 6..22
val allowableShiftSize = 4..6

// constants
val operatingDayLength = operatingDay.endInclusive - operatingDay.start


// declare drivers
val drivers = listOf(
        Driver(driverNumber = 1, rate = 10.0),
        Driver(driverNumber = 2, rate = 12.0, availability = 6..11),
        Driver(driverNumber = 3, rate = 14.0)
)


fun main(args: Array<String>) {

    buildModel()

    model.minimise().run(::println)

    // see variables for each driver
    drivers.forEach {
        println("Driver ${it.driverNumber}: ${it.shiftStart.value.toInt()}-${it.shiftEnd.value.toInt()}")
    }
}

fun buildModel() {

    //ensure coverage of entire day
    model.addExpression()
            .level(operatingDayLength)
            .apply {
                drivers.forEach {
                    set(it.shiftEnd, 1)
                    set(it.shiftStart, -1)
                }
            }

    // set objective
    model.objective().apply {
        drivers.forEach {
            set(it.shiftEnd, it.rate)
            set(it.shiftStart, -1 * it.rate)
        }
    }

    // driver-specific expressions
    drivers.forEach { it.addToModel() }
}

// Driver class will put itself into the Model when addToModel() is called
data class Driver(val driverNumber: Int,
                  val rate: Double,
                  val availability: IntRange? = null) {

    val shiftStart = variable().lower(6).upper(22)
    val shiftEnd = variable().lower(6).upper(22)

    fun addToModel() {

        //constrain shift length
        model.addExpression()
                .lower(allowableShiftSize.start)
                .upper(allowableShiftSize.endInclusive)
                .set(shiftEnd, 1)
                .set(shiftStart, -1)

        //add specific driver availability
        availability?.let {
            model.addExpression()
                    .lower(it.start)
                    .upper(it.endInclusive)
                    .set(shiftStart, 1)

            model.addExpression()
                    .lower(it.start)
                    .upper(it.endInclusive)
                    .set(shiftEnd, 1)
        }

        //prevent shift overlap
        drivers.asSequence()
                .filter { it != this }
                .forEach { otherDriver ->

                    val occupied = variable().binary()

                    model.addExpression()
                            .upper(0)
                            .set(otherDriver.shiftEnd, 1)
                            .set(occupied, operatingDayLength * - 1)
                            .set(shiftStart, -1)

                    model.addExpression()
                            .upper(operatingDayLength)
                            .set(shiftEnd, 1)
                            .set(occupied, operatingDayLength)
                            .set(otherDriver.shiftStart, -1)
                }
    }
}

49 comments:

  1. Thank you for your article! I have an observation, however - I believe there is a more optimal solution.

    The solution your algorithm found was:
    Driver 1: 10-16
    Driver 2: 6-10
    Driver 3: 16-22
    6 @ 10: $60
    4 @ 12: $48
    6 @ 15: $90
    Total: $198

    However, if we take an hour away from driver 3 and give it to driver 2, we can save a little money while still meeting all of the criteria:
    Driver 1: 11-17
    Driver 2: 6-11
    Driver 3: 17-22
    6 @ 10: $60
    5 @ 12: $60
    5 @ 15: $75
    Total: $195

    ReplyDelete
  2. Okay, the source code in the GitHub repository produces your output which is correct. I think I corrected this mistake while writing this post, but forgot to update that snippit of the console output. Should be fixed now. Thanks for catching :)

    ReplyDelete
  3. We support all types of HP printer troubleshooting and service. Just enter the model number of your printer in 123hp.com/setup to identify the software and drivers your printer requires. Download and install it in your mac and 'Run' the file. The process is easy however if you have any doubts or queries regarding HP printers contact us.

    ReplyDelete
  4. Crackle, the most entertaining channel is now on Roku to entertain you. If you are new to this channel, use the page crackle.com/activate. Once if you activate the channel, the most interesting program collections are on its way. The categories include full-length movies, TV shows, and documentaries and much more.

    ReplyDelete
  5. The Colombia Broadcasting System, popularly CBS is an English commercial broadcast television based in the US. You can add this channel to your Roku, through the web link cbs.com/roku

    ReplyDelete
  6. Hello everyone , here’s your opportunity for you to achieve your dreams of being a multi million dollar rich through trading , I once loss all I got through trading but was fortunate to come across a woman with great virtue and selfless heart (Mary ) i was introduce to her masterclass strategy while searching online which has revived me of all my losses and made me gain more and more . With her unique strategy you are entitled to daily signals and instant withdraw ,be rest assured of getting a refund of all your loss investment with any platform that has denied you in one way or the other in getting your money . Mrs Mary masterclass strategy is simply the best for beginners and those that are finding it difficult to succeed through trading she’ll help you with just a simple step . Email her ( maryshea03 @ Gmail .com) WhatsApp +1 562 384 7738 . Remember this is absolutely free!!!

    ReplyDelete
  7. Hello everyone , here’s your opportunity for you to achieve your dreams of being a multi million dollar rich through trading , I once loss all I got through trading but was fortunate to come across a woman with great virtue and selfless heart (Mary ) i was introduce to her masterclass strategy while searching online which has revived me of all my losses and made me gain more and more . With her unique strategy you are entitled to daily signals and instant withdraw ,be rest assured of getting a refund of all your loss investment with any platform that has denied you in one way or the other in getting your money . Mrs Mary masterclass strategy is simply the best for beginners and those that are finding it difficult to succeed through trading she’ll help you with just a simple step . Email her ( maryshea03 @ Gmail .com) WhatsApp +1 562 384 7738 . Remember this is absolutely free!!!

    ReplyDelete
  8. Great post.I'm glad to see people are still interested of Article.Thank you for an interesting read........
    hp officejet 3830 Mac setup

    ReplyDelete
  9. Your article was really impressive.Do keep post like this . You can also visit my site if you have time.Kindly visit our site for 123.hp.com/oj3830 for additional info...

    ReplyDelete
  10. Goa is a romantic place for a couple to enjoy parties, birthday, honeymoon, anniversary. Get the best couple tour packages for Goa at Republic Holidays Travel Services. Book 4 Nights 5 Days Goa Package for couples and explore the most beautiful beaches. Goa tour will be a lifetime experience. Call now, the best services guaranteed.Call@9911544266. https://www.republicholidays.in/goa-tour-package

    ReplyDelete
  11. Most likely the best obliteration of an extensive part of the free blogging websites is the manner in which that they don't allow you to do your own advertising. Ideally cuoc bong 88, you may have the option to link out to your site page where your advertising abides. Various site administrators have found that the free blogging websites blog magazine are astoundingly significant for doing this. Regardless, if muaveso you are not impelled enough yet to have your own domain name, you should look out one of the services that grants you to add advertising really to your blog.

    ReplyDelete
  12. Putlocker may be a seamless portal for television, series, movies, songs, etc. for free. it's not mainly for any film or language, from Hollywood to Bollywood, one can have access to each possible movie. In fact, unlike other portals who operate completely illegally, Putlocker has an official site named Putlocker.com that a broad base of individuals is mad.

    For the official content recorded, over 1.6 million people worldwide made access to it portal on a per-day basis. because of the piracy issues, it faced a report by motion picture Association of America, which led to its pack up by the united kingdom court Order. it had been considered together of the highest 250 sites visited by the people worldwide. Although, thanks to its massive fan base, Putlockers remains operated under different proxy websites.

    ReplyDelete


  13. Like all other on the internet streaming web-sites showcased in this post, 123Movies doesn’t truly host any content material on its servers. Alternatively, all content material is furnished by non-affiliated 3rd parties, producing 123Movies no less than relatively authorized.

    Putlocker is The most well-known online streaming web-sites on the internet. The location is so renowned that it has prompted many significant internet support providers to block access to it, depriving lots of its lengthy-time period supporters from savoring hassle-free entry to flicks and television shows. All Web-sites updated on May perhaps 16, 2020

    ReplyDelete
  14. What is the Roku Account?
    The Roku account is an essential requirement and you need to activate the Roku TV. It would be better if you already have a roku.com/link account else, you need to create the Roku account before getting into the activation process. If you want more information about Roku please visit our site Roku TV Setup

    ReplyDelete
  15. How to Activate Twitch channel on Roku?
    If you are new to the Roku streaming platform, select the best device among Roku Express, Express Plus, Premiere, and Premiere plus. Complete the setup and then navigate to the store. Add the channel to find the activation code. This code must be provided on the page, Twitch.tv/activate. Sign with the Twitch TV channel account, if required. If you are expecting any help to complete Twitch TV channel activation, talk to our network by dialing our number +1-855-718-4111 and also you can visit our site twitch.tv/activate

    ReplyDelete
  16. YouTube TV channel activation guide
    You can select the compatible device, add the channel, and then proceed with the settings to collect the activation code. This code must be provided on the page, tv. Sign in with the channel account, if necessary. For help and support to execute YouTube TV channel activation, please Visit our site tv.youtube.com/activate

    ReplyDelete
  17. How to remove Epson PRINTER CARRIAGE JAM ERROR? Dial toll free, troubleshooting steps by step jam error solution are here.

    ReplyDelete
  18. Great share!

    Loved all that you shared, and you are right, one cannot deny the power of Google. I simply love it and am part of a number of
    communities too. The point you mention in your post Sand blasting machine works that are very useful for me. I understand the way of the attractive to the customer with the products.

    Keep it works and share with us your latest information. For more Information or any help then visit now at Sand Blasting Machine, Sand Blasting Machine for Sale, Sand Blasting Machine Manufacturer, and Small Sand Blasting Machine.

    They are similar to small communities that you own - check them out if you haven't already.It's all got a lot better than before!t.

    Thanks for sharing. Have a nice week ahead.

    Visit at :www.blaster.co.in

    Regards,
    Purnima Sharma.

    ReplyDelete
  19. Find HP Printer Customer Support service options including driver downloads, diagnostic tools, warranty check and troubleshooting info.

    ReplyDelete
  20. Due to poor technical knowledge, I am getting jam in the middle of the setup process of the HP printer. What to do? I am unable to guess the setup process of the HP printer. It has become a risky task for me, so I need to take the master technical help from a certified technical specialist. I am sharing the 123.hp.com/ojp8710 HP setup procedure, this discussion with all of you, guys. So please anyone can urge the simple ways to set up the HP printer perfectly. Your guidence would be praise.

    ReplyDelete
  21. I would like to say that this blog really convinced me to do it! Thanks, very good post. Error Code 130

    ReplyDelete
  22. An obligation of appreciation is paying little cerebrum to together for putting the push to design this, I feel from an overall perspective about it, and I worth getting more familiar with it. On the off chance that conceivable, when you make information, OK need to restore your blog with more data? It's genuinely major to me. If you have some problem with Hp Recovery Mode windows 10 click the link.

    ReplyDelete
  23. you Can play daily online ,live cricket games, cricket online games,best cricket games,play cricket online, cricket game online at best cricket games app for android phones to win real cash and amount to instant approve in your bank or paytm wallet.
    cricket online games
    play cricket online

    ReplyDelete
  24. Nice Information , Thanks For The Great Content

    Get started to Install, Setup, Connect, Print from your 123 hp setup printers. Easy to Download driver & Printer software from HP Envy,HP Officejet,HP Officejet Pro,HP Deskjet Printer Setup Driver Installation

    For More Support

    123.hp.com/Setup
    123hp com/oj8049

    123.hp.com/oj3830
    123.hp.com/oj4650
    123.hp.com/Setup ojpro
    123.hp.com/ojpro6968

    ReplyDelete
  25. Thankful to you for sharing this key data! Need you will stick doing such an exercises you're doing. The HP E2 Error Code indicate that miscommunication between the PC and HP printer.

    ReplyDelete
  26. Tumbling to pull out your blog again, it's been quite a while for me. I need this article to finish my school task. Grateful to you. Hi I providing hosting in 80% off. For more information visit my website click here Hostinger Coupon 2021

    ReplyDelete
  27. Is your Epson item printing clear pages? Some of the time, the Epson printer prints void pages for both dark and shading records. All things considered, there can be a few reasons liable for the clear page printing issue of your printer. How to fix Epson printer printing blank On the off chance that it isn't printing clear, the dark ink cartridges may be unfilled or if there should be an occurrence of shaded print activity, shading cartridges may beneath. In a couple of cases, your Epson printer probably won't work effectively consequently showing a clear page issue. Print heads obstructing or any off-base printer setting may likewise prompt void page printing. To fix Epson printing clear pages issue, this blog entry guides you with two distinct answers for two diverse working networksusers.

    ReplyDelete
  28. There is the point at which your printer won't print in dark and subsequently make this issue an excess of bother. At such a point in time, despite being troubled, you have to research the ink cartridges 123.hp.com/envy4512 and ensure you use only real HP cartridges.

    ReplyDelete
  29. Really thanks for sharing this useful post !! This post is very informative and helpful for my business related to subscription management platform.

    ReplyDelete
  30. Great post! I have definitely noted some points from it. It feels crazy how a well-composed assignment can have a strong impact on the grades. To help you achieve your academic goals, the MyAssignmentHelpAU platform has curated an exclusive assignment help facility to help students enjoy their academic journey.
    For More info visit here: Assignment Writer Australia

    ReplyDelete
  31. If you are not able to Download AOL Desktop Gold Windows, then you may visit our website for more information.

    ReplyDelete
  32. Just want to say your article is as astounding. The clearness in your article is just spectacular and i could assume you are well educated in this area of study. Till Death (2021) Bangla Subtitle .Thanks a bunch and please carry on the gratifying work.

    ReplyDelete
  33. Thanks a lot for sharing great post with us. I’m happy that you shared this useful info with us. We are Ethnicitywiki provides information about Ethnicity, Race of famous Personalities, Businessman, and Wiki's Celebrities of Famous Personalities. Get all of the world news of celebrities and stars.

    Visit now www.ethnicitywiki.com/

    Thanks again for sharing helpful information with us.

    Have a nice week ahead.

    ReplyDelete
  34. Have a site will permit you to show your things and associations and potentially appear at endless clients. The Epson Error Code 0x88 indicate ink system error in Epson printer. Epson executives help you to fix the issue.

    ReplyDelete
  35. Great Share!

    First off, congratulations on this post. If You are searching for latest government jobs in India. Vacancysquare provides you upcoming government jobs information like - Railway, Police, Banking Job, Defense, SSC, UPSC, UPSSSC, teacher jobs and other department for state and central government job. This bucket list for you to know the right jobs detail.

    This is really awesome but that's what you always crank out my friend. Reading your story got my face leaking and my heart singing. Find qualification based government job and vacancies with employment news at: "www.vacancysquare.com". Also check govt jobs, private jobs, latest jobs, teacher jobs, bank jobs, railway jobs, police jobs, UPSC jobs, SSC jobs, UPSSSC jobs, sarkari result with employment news.

    Great posts we can share to our users. Check the latest government job vacancies, upcoming government Indian jobs, latest notifications for government jobs, today government jobs notifications or sarkari naukri now. Just visit our website www.vacancysquare.com to get your dream job notification today.

    Get a complete information about Government jobs in India, Latest jobs in India, Work from home jobs, admit card, exams, results, answer key, admission and syllabus of Govt Indian jobs.

    If you are interested in free guest post, advertising with us, writing for us and paid guest post service to contact us & about OR visit on our job notification portal www.vacancysquare.com.

    All the best, I’m excited to watch your journey! Keep it works and share your amazing thoughts.

    Great share and thanks again for the mention here,

    Thanks and Regards!
    Priya Singh.

    ReplyDelete
  36. It would be pretty difficult to set up their HP Officejet Pro Printer in a suitable way. The unprotected printer users may vary for generative instruction for the printer tool. That’s why; our technical engineers have fixed to advance a website i.e., HP Officejet Pro to give more information concerning HP Officejet Printer setup. So, if some users give access to this link, they will get to study how helpfully printers should be set up. Once the printer has effectively been set up also in a direct format, the users can flexibly print anything from their HP Officejet Pro Printer system.

    ReplyDelete
  37. Your Information is so informative. Thank you for sharing this information with us. Keep sharing again.
    If are you worried about How To Find Jio Tower Near Me for installation according to my budget. So, don’t worry about it. Because Jio Digital Tower is here to offer you necessary jio installation services with advanced technology on your budget. To Find the Best Jio Digital Tower Solutions, visit the website.

    ReplyDelete
  38. Wonderful Information. I see your post. Thanks for sharing with us.
    Are you search Electrical Panel Repair services in Rancho Cucamonga. Todd Peters Electric offers reliable electrical services with skilled & trained electricians. To repair your damaged electrical panel, visit our website.

    ReplyDelete