Amāzolve
Welcome to the Amazolve advanced solver solution. Designed for IT professionals, Amazolve is a fresh approach to solving multivariable and combinatorial problems with a concentration on easy and intuitive problem definition, high performance compute environments, and simple standard integration tools. This documentation is divided into the following section:
- Concepts: Covers the core concepts of solver problems as well as the basics of Amazolve.
- Implementation: Describes the process by which business requirements (constraints) are discovered, and implemented/tested iteratively within Amazolve.
- Problem Definition Reference: Describes the details of the problem definition file and related atom and array resource files.
- Client Interfaces: Describes programmatic interfaces to Amazolve.
- Samples Overview: Describes the samples provided in the supporting GitHub repo.
Solver Problems
A solver problem is a multivariable or combinatorial problem without an obvious mathematical solution. While that may sound like a mouthful, these types of problems are faced by everyone, nearly every day. For example, deciding which route to take when going to a meeting, or creating a schedule for a significant number of employees. These problems often have no known best solution - for example, did I choose the fastest route (even if I used Google maps)? Did I create the most efficient schedule?
In smaller cases (e.g., fewer options for the route to take or fewer employees to schedule), these problems can sometimes be solved exactly, but they grow more complex exponentially given the number of options (i.e., variables).
Solver problems are generally defined by three key components:
-
Variables: These are the output of the solver, or what is trying to be solved. For example, the different options for traveling from point A to point B or the shifts and days and employees for a schedule.
-
Constraints: These are mathematical definitions (i.e., formulas) of what is allowable in the solution space - or the “rules” of the problem. For example, in the routing case, certain vehicles are not allowed by law on certain roads. Or in the scheduling case, certain employees can only work one weekend per month.
For example, here is a constraint taken from the supply-chain (see "PBS" demo) sample problem. This constraint simply states that the origin and destination must be different for a segment, given it makes no sense to ship something to the same place!
\(\sum_{\left(r,s\right)\in\phi}\sum_{i} x_{ri}x_{si} = 0\)
-
Objective: This is also a formula that is to be minimized or maximized. In the routing example, an objective could be to minimize distance or time. In the scheduling example, the objective could be to minimize over or under staffing while maximizing employee shift requests.
For example, here is the objective function from the supply-chain sample problem.
cost
is a matrix which defines the expense of going from location r to location s and the objective is to minimize the sum of all segments in the route:\(C = \sum_{\left(r,s\right)\in\psi}cost(r_a,s_a)\)
Solver Problems in Amazolve
In Amazolve, variables are represented as an two dimensional (2D) array of “atoms” and constraints and objectives are defined in a problem.json
file using nested summations and expressions. The mathematical formulas that define a specific problem (the constraints and objective) are always defined as summations - for example, the sum of all segments of a route, or the sum of all hours an employee works. Thus, Amazolve uses this to organize the problem.json
file and in fact the entire problem definition itself.
Given the sample constraint and objective formulas shown above, here's how they would look in Amazolve's problem.json
file:
- Constraint:
{
"constraint": {
"CID": "C2",
"comment": "Constraint #2 - Origin and destination must be different",
"penaltyVar": "hardPenalty",
"sumIter": "iterArray",
"arrayName": "phi",
"iterVars": [
"r",
"s"
],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "S",
"iterVars": [
"i"
],
"exprMain": "Xr(r, i) * Xr(s, i)"
}
]
}
}
- Objective:
{
"constraint": {
"CID": "C",
"sumIter": "iterArray",
"arrayName": "phi",
"iterVars": [
"r",
"s"
],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "S",
"iterVars": [
"i"
],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "S",
"iterVars": [
"j"
],
"exprMain": "cost(r, i, j) * Xr(r, i) * Xr(s, j)"
}
]
}
]
}
}
The Atom Array
Solver variables are represented within Amazolve via a two dimensional (2D) atom array. The atom array is termed a "resource, time, state" array, or RTS array with each row of the array being a "resource", each column is a unit of time, and each individual cell an "atom". Atoms can be set to a "state" - the atom is the cell, the state is the value the atom is set to.
Note: While the atom array is 2D, Amazolve does offer a way to refer to the array using a third, "bit" dimension (see the X() expression functions).
Generally speaking, throughout Amazolve, a capital letter refers to the count and a lower case letter reverse to a single element. So, "R" would be the count of resources (or rows) while "r" would refer to a single row index. "T" and "t" similarly refer to the count of time columns or a single column, respectively.
"S" and "s" refer to the count of states and an individual state as well, however there is special significance to identifying whether the states being represented allow for a zero state meaning no state. In some problems there is no empty state and in others there is which can be handled differently in the contraints.
Note that the terms for these dimensions do not always refer to actual resources or points in time, but often times (particularly in scheduling problems) they do. For example, a traveling salesman problem (TSP), which is a very common multivariable problem easily solved with Amazolve, is represented as a single row (the individual salesperson) and time columns represent the order of the locations visited, and each atom state must be unique and represent the location to be visited:
Another example would be an asset allocation or portfolio optimization problem. In this case, atoms represent quantities of a given asset of an overall portfolio. The atom array would be a single column with each row representing the asset. The state would then take on some quantity (e.g., 0 to 100 states) that when normalized defines the percentage of that asset that is part of the portfolio:
Finally, a typical employee scheduling application would have multiple rows where each row is an employee, each column is a day, and each atom state contains a shift that is assigned to that employee on that day. Note that this is a case where a zero state represents a day off, or no shift assigned:
Atoms Versus Bits
If you are familiar with linear programming representations of these problems, or more specifically, Quadratic Unconstrained Binary Optimization (QUBO) methods, you may have noticed that atoms are not bits. In most QUBO formulations, individual states are represented by a series of bits and often one sees this as an array - the ubiquitous \(x_{ij}\) variables representing bits in a two dimensional array for example.
Let's take a brief conceptual walk from bits to atoms as this should shed some light on solving multivariable and combinatorial problems, integer linear programming, QUBO and even quantum computation to some degree.
Note that in all the following examples, there are many alternatives, details, and possible arguments around formulation that are ignored at the expense of keeping the concepts simple and specifically focused on Amazolve underpinnings. There are a ton of other resources for learning about any and all of these concepts in greater deatil.
TSP With QUBO
Given a traveling salesperson problem (TSP) where the salesperson must visit six different locations and we wish to find the shortest path. Starting with the QUBO, or binary optimization method, we would typically create a two dimensional array where both the vertical and horizontal axes are the six locations - so a 6x6 array:
In each cell of the array there is either a 1
or a 0
signfiying whether the path goes from the
row's location to the column's location. The above array thus shows a path from 4 -> 1 -> 2 -> 3 -> 5 -> 0 -> 4,
a circular path. Assuming there is function (called cost
) which stores or calculates the distances from all locations to each other,
then a simple objective function (to be minimized) would be:
\(C = \sum_{ij} x_{ij}cost(i,j)\)
Notes:
- Where there is a 1 in the array, the cost is included in the sum.
- Given a bit can have two states, 0 or 1, and there are \( 6 * 6 = 36 \) elements in the array, then the total number of possible combinations of states within the entire array is \( 2^{36} = 68,719,476,736\). Therefore, any solver algorithm must sift through over 68 billion possible combinations to find the best path.
- Several constraints are required for the QUBO array to work correctly. We must have constraints that mandate that there is only a single 1 in any column or row. Thus, at least two additional constraints must be defined (and calculated as part of solution evaluation).
- In addition to the aforementioned constraints, the objective function calculations increase exponentially based on the number of locations that must be visited.
Seems like a ton of work just to find the correct order of 6 locations!
TSP A Little Better
As you may realize, we don't need 6 bits (rows), to represent a single value from 0-5. In other words,
we might think to only use 3 bits where those 3 bits are the binary represenation of locations 0-5. So,
bits 000
would be location 0, and 011
would be location 3, on so on. Then, we could make the six columns
simply the order in which the locations are visited. This would give us a array like the following:
Notes:
- This is not an actually feasible way to organize a solver atom array as the contraints would be rather strange, but the purpose is to show a reduction in solution space.
- Now that we have 3 rows and 6 columns, the number of possible combinations of bits in this array would be \( 2^{18} = 262,144\). Well that's an enormous improvement! We've taken the possible solution space from over 68 billion to "only" 262,144.
Still, seems like a lot of space to search just to find the order of 6 locations, and this formulation isn't very practical.
TSP On Atoms
Finally, we return to the atom array of the TSP problem:
Assuming again there is function (called cost
) which stores or calculates the distances from all locations to each other,
then a simple objective function (to be minimized) in this case would be:
\(C = \sum_{t}^{T-1} cost(a_{t},a_{t + 1})\)
Here's the same objective function in Amazolve:
{
"constraint": {
"CID": "O1",
"sumIter": "iterVar",
"exprFrom": "0",
"exprTo": "T - 1",
"iterVars": [ "t" ],
"exprMain": "cost(At(t), At(t + 1))"
}
}
Notes:
- One could argue that this is the same as the prior example, we're just not looking at the individual bits anymore. Well, precisely!
- Now, let's suppose that when we create an initial atom array, we specify that:
- Each atom's location assignment is random and unique.
- When searching the solution space, only reordering of the atoms is permitted.
- Given the atom array, and the search settings, we now have \( 6! = 6 \cdot 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1 = 720 \) possible combinations in the solution space.
- There are no constraints required for maintaining any uniqueness of "bits".
- The number of calculations in the objective function increases linearly (not exponentially) with the number of locations.
Thus, the atom array formulation now reduces the search space to just 720. From 68 billion, to 720!
Why Use Bits?
Great question. It becomes clear that representing the problem optimally can drastically reduce the amount of work
the solver has to do in finding a solution. All of the above models will, and do work. In Amazolve, one can use the
X()
expression function, or the A()
expression function - one referring to the bits, one to the atoms. In fact, they can be used within the same problem definition. The X()
function just has an extra (technically unnecessary) dimension.
So why would one use bits versus atoms? This is a long answer but the short of it is that it all comes from linear programming (LP) and integer linear programming (ILP). The bit, or \(x_{ij}\), method is easy to translate to an ILP problem where each \(x_{ij}\) is a variable with a coefficient that then forms a tableau that is solved with some form of the simplex method. The problem is that many problems have variables that aren't linearly related. LP uses decimal values for variables and assumes some sort of linear relationship amongst them and the problem space in general. It can slice between variable values to find points within a theoretical simplex (shape) when it is searching for solutions.
ILP adds the requirement that the variables must be integers, not decimals. However, ILP simply relaxes the integer constraint, and will split decimal values (by rounding up or down) into round decimals to create branches within the LP simplex solving process. Thus, this method is called "branch and bound", but is still fundamentally LP.
If you have a scenario where states within the atom array correspond to something like a look-up table, or a distance function, for example, where there is no linear relationship that results from the variables to the output, then on significant sized problems, the LP and ILP methods generally perform no better than other methods.
LP/ILP in Amazolve?
Amazolve is designed such that whatever formulation you choose for the atom array, and however you write constraints within the problem file, future support for realizing (or rendering) the problem into LP and ILP will be available. Amazolve's problem definition syntax is designed to be materialized, or compiled, into many different solver algorithms available now (e.g., ILP, metaheuristic) and in the future (quantum computing).
Unconstrained Constraints
A key aspect to Amazolve solving and to QUBO models in general are unconstrained constraints (i.e., the "U" in QUBO). In linear programming (LP) formulations, constraints are generally hard. For example, take a constraint such as "5 employees with skill set A must be on staff at all times". A LP formulation would have a "hard" requirement that summed up the number of employees with that skill set A and would reject any possible solution that did not have that. A hard, all or nothing sort of constraint.
An unconstrained model would "penalize" the solution's score if the constraint is not met, but allow the solution to continue being investigated by the solver. So, given the same example, an unconstrained solver would attach a penalty factor, say 1000, to the number of employees under the required amount. If a potential solution only had 3 employees on a given day with the required skill set (where 5 are required), then the 2000 would be added to the score. Assuming we are minimizing the solution score, then a solution with 4 employees would score better than one with 3 (1000 vs. 2000) and therefore "push" the solver in the direction of a better solution.
This is how all constraints, and for that matter, all problems are defined in Amazolve. Potential solutions are scored given penalty factors and objective functions, and are either minimized or maximized.
Unconstraints In Amazolve
There are two key aspects to understand in the Amazolve problem definition (PD) that define the scoring and penalty aspect of unconstrained optimization:
-
Expression functions
EQ()
andIQ()
, or "equality" and "inequality" respectively. As their names imply, these functions define equality and inequality relationships amongst problem variables. But they do more than that - they also return how unequal the variables are. So, for the aforementioned employee case, a constraint expression withEQ(emp_count,5)
would return2
if there were only 3 employees on staff.Similarly, if the constraint description stated that between 3 and 5 employees must be on staff with a given skill set, the constraint expression may look like
IQ(3,emp_count,5)
, corresponding to the inequality: \( 3 \le emp\_count \le 5\). If there were only 1 employee on staff, this would return2
, and if there were 7, it would also return2
.The
IQ
andEQ
expression functions also serve to mark equality and inequality relationships used to formulate linear programming solutions. So while PD expressions also support=
and!=
operators, for example, these do not behave the same as the inequality expression functions. -
penaltyVar
: Within the PD, any constraint can be assigned apenaltyVar
which refers to a constant defined within the PD'sconstants
section. This is multiplied by whatever the constraint's sum result is. For theIQ
andEQ
examples mentioned above, this would be multiplied by the2
values returned for example.
Next, is a complete example of a constraint with an inequality and a hard penalty. This is a complex constraint so all details need not be understood at this point, but just a few key points:
- The
penaltyVar
for the constraint ishardPenalty
which is defined in the constants block below. - The
exprMain
(main expression) of the second sum applies the inequality function. Note the left side of the inequality is the~
symbol, which in the Amazolve PD refers to infinity. This constraint is from the nurse scheduling sample and implements the requirement that a given nurse can only be assigned a maximum number of shifts of a given type described in an external arraymax_shifts
. TheIQ
in this case is declaring "the total number of shifts must be less than or equal to the given max". If the max_shifts is exceeded, the count over is returned byIQ
and then multiplied by 1000.
{
"constraint": {
"CID": "C3",
"comment": "Constraint #3 - Max Shifts",
"penaltyVar": "hardPenalty",
"sumIter": "iterDim",
"iterDim": "R",
"iterVars": [ "r" ],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "SZ",
"exprMain": "IQ(~, sum_shifts, max_shifts(r, s))",
"iterVars": [ "s" ],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "T",
"exprMain": "A(r, t) = s",
"iterVars": [ "t" ],
"resultVar": "sum_shifts"
}
]
}
]
}
},
{
"constants": [
{
"name": "hardPenalty",
"def": 1000
}
]
}
Implementation Overview
This section covers the process used in implementing a solution with Amazolve. The flowchart below shows the high level steps that are a part of the process, however, the steps do not necessarily need to be done serially and the process is iterative. It is important to note that the words description and definition are used throughout and have distinctly different meanings. A description is a written scenario or story that describes a constraint or objective in human terms. A definition is an actual configuration - be it a file or API call.
Throughout this guide, references are made to Amazolve's Problem Definition, or PD. This is the actual configuration (JSON file, for example) of the atoms, objective, constraints and supporting arrays that create the specific solution. In addition, the PD file can also be a "markdown file" (MD) with JSON islands contained therein. When a PD is constructed as a MD file, the "description" and "definition" reside within a single file that can communicate to all stakeholders as well as potentially outside compliance actors. (A PD as MD will be discussed in greater detail in the "Compliance" section of this guide.)
In addition, the following are roles that are part of the process.
- Stakeholders are familiar with the business problem, or need, and the high level requirements.
- Analysts generally are familiar with the data and/or mathematics involved in describing the business requirements.
- IT is responsible for the technical implementation of the Amazolve product - the configuration of both data and problem in the PD.
Note that it is absolutely the case that all of the above roles could be the responsibility of one person, or roles could be shared amongst several people.
Step: Problem Description
-
Roles: Stakeholders, Analysts, IT
-
Actions: At this step, the fundamental problem is described. For example, a delivery route that needs to be optimized given certain time and resource constraints, or a schedule that must be created given certain employee availablity or job requirement constraints. The atom array is defined at this point as it forms the basis for the entire solution - what will be the resource rows, time columns and what states are in each atom.
The atom array defines how the solution answer, or the "output" is defined as this will be consumed by whatever upstream system is trying to integrate the results.
In addition, it is important at this point to describe the frequency, latency or data flow needs of the solution. For example, the solution may need to run nightly, hourly, or on demand. This will also start to highlight where and how the data for the solution (e.g., employee lists, capabilities, resource availability, orders, etc.) may be acquired and prepared for the solution.
Step: Constraint Description
-
Roles: Stakeholders, Analysts, IT
-
Actions: Constraint description is broken out from Problem description to show that a few constraints at a time can be tackled rather than all at once. Most solutions have many constraints (a dozen or so is normal) and many are discovered as the process moves along. Therefore, it is recommended a few (or one) constraint(s) are chosen for initial implementation.
For example, for a routing application, just the objective function to minimize distance would be a logical place to start. For an employee scheduling application, the maximum number of days each employee can take off during a time period. Describing the constraints at this step lead naturally into the next step.
Step: Data Source Identification
- Roles: Analysts, IT
- Actions: This step identifies what data sources will supply the required data for the constraints described int the prior step. For example, employee information may come from an HR system, or sales or work order information may reside in an ERP. This data may be easily available via a CSV file, or a database, but will ultimately need to be accessed or generated based on the frequency of use of the solution described in the first step. Additionally, the same source(s) may be used for multiple constraints, just with additional data points (fields) added so the data source identification should be flexible during implementation.
Step: Data Source Translation
-
Roles: IT
-
Actions: In order to use the raw data identified in the previous step, it must be converted into an standard json array of up to four dimensions. Arrays are used in two ways within a PD:
- Lookup: An array can be a 2 or 3 dimensional lookup table (in essence). For a routing application, for example, this may hold the distance between all locations. When used as a lookup, the last dimension can optionally act as a set of fields (see,
ldimFields
). - Iterable, or Set: An array can also be used for iteration within a sum - in this case the array must be 2D and can be thought of as a "table" (in the classic RDBMS sense) or a "set" (in the algebraic sense).
For example, in the formula below, \(cost(r_a,s_a)\) is an array with \((r_a,s_a)\) indexing into the returned value, \(\psi\) is an array with \(\left(r,s\right)\) being the fields
r
ands
of each row of the 2D array.\(C = \sum_{\left(r,s\right)\in\psi}cost(r_a,s_a)\)
Regardless of how the array is being used, they are defined physically as JSON files that are referred to by the PD.Therefore, "translation" involves converting the raw data sources into either an array JSON file that can be consumed by the constraints in the PD.
- Lookup: An array can be a 2 or 3 dimensional lookup table (in essence). For a routing application, for example, this may hold the distance between all locations. When used as a lookup, the last dimension can optionally act as a set of fields (see,
Step: Constraint Definition
-
Roles: Analysts, IT
-
Actions: In this step, constraints are implemented within the PD. The translated data sources and constraint descriptions are combined to formulate the constraint definitions themselves. For example, taking the previous objective function...
\(C = \sum_{\left(r,s\right)\in\psi}cost(r_a,s_a)\)
..and converting it into the PD definition...
{ "constraint": { "CID": "C", "sumIter": "iterArray", "arrayName": "psi", "iterVars": [ "r", "s" ], "exprMain": "cost(At(r), At(s))" } }
Step: Testing
- Roles: Stakeholders, Analysts, IT
- Actions: In this step, the constraints (and objectives) that have been implemented thus far are tested. It is important to not only test the basic functionality using a static data set, but also to test the raw data pipeline by using new raw data and running through the translation process, running an solver session, then verifying the returned atom array is consumed correctly in the upstream system or displayed as desired to an end user.
Compliance
In addition to optimizing complex solver problems, Amazolve is also designed to help with compliance and regulatory requirements. A control, in business compliance terms, is something that implements a compliance "check" and Amazolve problem definitions (PDs) can act as controls for the process they are optimizing.
For example, the number of consecutive hours a truck driver (e.g., short haul, or LTL) can drive is strictly regulated by the government. A trucking company needs a system to manage this compliance that tracks driver hours in addition to optimally assigning customer orders and routes to drivers within the constraints of that compliance. These hourly constraints naturally become Amazolve PD constraints for optimization purposes, but also demonstrate to regulators that a control is in place to assign work in a compliant manner. In healthcare and many other sensitive regulated industries, clear demonstrable controls can save millions of dollars in fines, and speed compliance checks by regulators.
Compliance Communicated
Companies have to do more than simply state that they are compliant or that they have controls in place. They have to prove it. To support this, Amazolve has a unique feature that creates seamless documentation across business users, analysts, IT, and regulators: problem definitions (PD) as markdown (MD) files. In other words, the PD file that Amazolve uses can either be a raw JSON file, or an MD file with islands of JSON objects embedded within.
MD files are ubiquitous for documentation on the Web. They are easier to write than HTML, and are designed specifically for writing documentation. A simple Google search for markdown will yield a plethora of tools, guides and examples of markdown. In addition to the text documentation, MD also supports LaTeX which is a language for writing mathematical formulas, and supports embedded JSON islands. In fact, all of the Amazolve documentation (what you are reading now) is built using MD, LaTeX and JSON islands!
When describing compliance to a reviewer, or business user, a single MD file can show the process description, the mathematical constraints, and the constraint definitions as implemented in Amazolve - and that is the actual file that Amazolve uses during it's processing. There is ongoing 100% fidelity of the process from description to implementation.
In this sense, Amazolve solutions are self-documenting, repeatable, and compliant.
In a nutshell:
- Problem and constraint descriptions (narratives) are written in plain text within the MD file.
- Mathematical descriptions of constraints and objectives are written in LaTeX.
- The Amazolve PD constraint is written as a JSON object island within the MD.
- Amazolve is given the entire PD as configuration, and extracts just the JSON islands for the problem definition.
A Complete Example
The following example (everything below the line) is taken from the supply-chain (PBS meaning product breakdown structure) sample. This is of course an MD file so you are viewing it as a viewer would, however you can review the underlying MD file in the sample GitHub repo.
Imagine refering to this as a documented control from your compliance spreadsheet!
PBS Constraints
Constraint 1: One and only one assignment of the site per part:
\( \sum_{i} x_{ri} = 1 \enspace \forall r \)
{
"constraint": {
"CID": "C1",
"comment": "Constraint #1 - One and only one assignment per part",
"penaltyVar": "hardPenalty",
"sumIter": "iterDim",
"iterDim": "R",
"iterVars": [
"r"
],
"exprMain": "EQ(sum_assigned, 1)",
"sums": [
{
"sumIter": "iterDim",
"iterDim": "S",
"iterVars": [
"i"
],
"exprMain": "Xr(r, i)",
"resultVar": "sum_assigned"
}
]
}
}
Constraint 2: Origin and destination for each transport must be different:
\( \sum_{\left(r,s\right)\in\phi}\sum_{i} x_{ri}x_{si} = 0 \)
{
"constraint": {
"CID": "C2",
"comment": "Constraint #2 - Origin and destination must be different",
"penaltyVar": "hardPenalty",
"sumIter": "iterArray",
"arrayName": "phi",
"iterVars": [
"r",
"s"
],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "S",
"iterVars": [
"i"
],
"exprMain": "Xr(r, i) * Xr(s, i)"
}
]
}
}
Constraint 3: The origin of two sub-parts of a common part must be different:
\( \sum_{\left(r,s\right)\in\psi}\sum_{i} x_{ri}x_{si} = 0 \)
{
"constraint": {
"CID": "C3",
"comment": "Constraint #3 - Origins of two subparts must be different",
"penaltyVar": "hardPenalty",
"sumIter": "iterArray",
"arrayName": "psi",
"iterVars": [
"r",
"s"
],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "S",
"iterVars": [
"i"
],
"exprMain": "Xr(r, i) * Xr(s, i)"
}
]
}
}
Objective: Cost in transport between chosen sites must be minimized:
\( C = \sum_{\left(r,s\right)\in\psi}cost(r_a,s_a) \)
{
"constraint": {
"CID": "C",
"sumIter": "iterArray",
"arrayName": "phi",
"iterVars": [
"r",
"s"
],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "S",
"iterVars": [
"i"
],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "S",
"iterVars": [
"j"
],
"exprMain": "cost(r, i, j) * Xr(r, i) * Xr(s, j)"
}
]
}
]
}
}
PBS Constants
Symbol | Description |
---|---|
hardPenalty | Penalty applied to unmet constraints |
{
"constants": [
{
"name": "hardPenalty",
"def": 10000
}
]
}
PBS Arrays
Symbol | Description |
---|---|
cost | A database of costs of moving a given part between two locations |
phi | The product breakdown structure's parent/child part relationships |
psi | The product breakdown structure's sibling part relationships |
[
{
"array": {
"name": "cost",
"file": "array_cost.json"
}
},
{
"array": {
"name": "phi",
"file": "array_children.json"
}
},
{
"array": {
"name": "psi",
"file": "array_siblings.json"
}
}
]
PBS Atoms Configuration
A standard random initialization of the atoms is used and random mutations (or permutations) are applied during the search process. In addition, the atoms are intialized externally, depending upon the size of the part list and number of locations.
[
{
"params": {
"initializeType": "Random",
"mutatorType": "Random"
}
},
{
"atoms": {
"file": "atoms.json"
}
}
]
File Structure
As described earlier in this guide, the problem definition (PD) file can be either a JSON file or a MD file but for ease of explanation, only the JSON file is shown in the examples in this section. The PD is an array, that is, it starts and ends with an [
and ]
. Objects within the array are described within this section.
Generally speaking, the PD is one big sum (summation) - each constraint is defined by nested sum's, and all constraints and objectives are summed together to create a score for a given potential solution. All other objects (e.g., params, arrays) are there to serve the sum in some way.
array
This object is an array that can be used (by name) within PD expressions or a sum's arrayName
. When used by name within an expression, it looks like a function. The dimensions of the array are implied by the underlying JSON file that is being referred to.
Example:
{
"array": {
"name": "cover_array",
"file": "array_cover.json",
"ldimFields": [
"req",
"wu",
"wo"
]
}
}
The above array used in an expression...
EQP(sum_shifts, cover_array(t, s, req), cover_array(t, s, wu), cover_array(t, s, wo))
...or as the array for a sum...
{
"sumIter": "iterArray",
"arrayName": "cover_array",
"iterFields": [
"r1",
"w2",
"w3"
]
}
Fields:
name
: The name to define for this array that is used within expressions.file
: The external JSON file that contains the matrix data.ldimFields
: This names the last dimension indeces for representation of a "record". As shown above by the use of thecover_array
in theEQP
expression function, the last dimension index is referred to by the names given. In other words,req
would be 0,wu
, 1, andwo
2.
Note that iterFields
is used when iterating over the array to name fields, not ldimFields
. This is to allow for multiple nestedsum
's to iterate over potentially the same array.
atoms
This object defines the atom structure and, optionally, an initial array of atoms. The structure can be defined within the PD in the atoms object or the atoms object can refer to an external file for dynamically generated atom definitions:
Example 1 (Inline):
{
"atoms": {
"resourceCount": 2,
"timeCount": 1,
"stateCount": 100
}
}
Example 2: (External):
{
"atoms": {
"file": "atoms.json"
}
}
Fields:
resourceCount
: Defines the number of resources (the number of rows of the atom array).timeCount
: Defines the number of time atoms per row (the number of columns of the atom array).stateCount
: Defines the number of states within each atom.file
: Defines an external JSON file that contains the atom configuration (in the same format as the object).array
: A JSON array that specifies the initial state of the atom array (in this case, params.initializeType is ignored). See also file references for more details on array and atom file formats.
constants
This object defines a list of named constants that can be used in expressions or other fields within the PD.
Example:
{
"constants": [
{
"name": "hardPenalty",
"def": 1000
},
{
"name": "coverPenalty",
"def": 500
}
]
}
Fields for Each Constant:
def
: The defined value of the constant.name
: The name to be used within other areas of the PD.
constraint
The constraint object is a nested list of sum
objects. Thus, the constraint object is and contains sum
objects. Fundamentally, a sum
is defined much like a for
loop in many programming languages. It has one or more variables that are being iterated over, start and end conditions, and something that goes on in the body of the loop. In this case, what goes on inside the body of the loop is defined by the exprMain
field containing an expression.
When the sum
is executed, child sums are executed before the exprMain
of the parent. The result of exprMain
is added to the overall solution score or collected in a variable for use by the parent, signaled by the use of resultVar
in the child sum.
In the example below, the parent sum
is iterating over dimension T (time) and state (dimension SZ in this case). For each row of the set, a child sum
is executed with the resultVar
of sum_shifts
. This resultVar
is then used in the exprMain
of the parent.
If resultVar
was not specified, then the child sum
's exprMain
would be added to the solution score, but since in this case it is specified, only the parent's exprMain
is added to the solution score. Details of all constraint
fields and the different type of iterators are given in more detail below.
Example:
{
"constraint": {
"CID": "O3",
"enabled": true,
"type": "objective",
"comment": "Objective: Cover Requirements",
"sumIter": "iterDim",
"iterDim": "T",
"iterVars": [ "t" ],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "SZ",
"iterVars": [ "s" ],
"exprMain": "EQP(sum_shifts, cover_array(t, s, req), cover_array(t, s, wu), cover_array(t, s, wo))",
"sums": [
{
"sumIter": "iterDim",
"iterDim": "R",
"iterVars": [ "r" ],
"exprMain": "A(r, t) = s",
"resultVar": "sum_shifts"
}
]
}
]
}
}
Root Fields:
CID
: Required. This is the constraint ID and must be unique within the PD. This CID is used for reporting purposes and in result data.type
: (Optional). Can be one of[hard|soft|objective]
but is not required. It is primarily for reporting and analysis purposes.hard
: This denotes that the constraint must be satisfied (in that it is minimized to zero) in order for the solution to be considered feasible.soft
: The constraint is not required to be satisfied for a solution to be considered feasible.objective
: The constraint is or is a part of the objective function.
comment
: (Optional). Purely descriptive information.enabled
: (Optional). Can be one of[true|false]
. Enables or disabled a constraint. All are enabled by default. This can be used for testing or debugging.
Fields for root and all child sums:
sumIter
: Required. Can be one of[iterArray|iterVar|iterDim]
. Each of these types has their own related fields, and are described in their own section below.iterArray
: The sum will iterate over rows of a given 2D array, with columns of that array named in order by theiterVars
. See below for this sum type's specific fields.iterDim
: The sum will iterate over either the R, T, or S dimensions of the atom array. See below for this sum type's specific fields.iterVar
: The sum will iterate over a start and end condition (expression). See below for this sum type's specific fields.
sums
: (Optional). Array of child sums.
Fields if sumIter = iterVar
:
iterVars
: Required. Names the variable that is being iterated over and available to expressions.exprFrom
: Required. The expression that defines the starting value of the variable (defined initerVars
). This can be as simple as0
or a complex expression such as(1 + T) / 2
.exprTo
: Required. The expression that defines the ending value of the variable. Depending uponexprToEq
, the sum will iterate either to one less than this value, or this exact value.exprToEq
: (Optional). One of[true|false]
. Sets whether theexprTo
is equal to or less than. By default, false.
Example:
{
"constraint": {
"CID": "O1",
"sumIter": "iterVar",
"exprFrom": "0",
"exprTo": "T - 1",
"iterVars": [ "t" ],
"exprMain": "cost(At(t), At(t + 1))"
}
}
Fields if sumIter = iterDim
:
iterVars
: Required. Names the variable that is being iterated over and available to expressions.iterDim
: Required. One of[R|T||S|SZ]
R
: Iterate over theR
(resource count) dimension of the atom array.T
: Iterate over theT
(time count) dimension of the atom array.S
: Iterate over theS
(state count) dimension of the atom array.SZ
: Same asS
but starts at 1 instead of 0. This is for cases where the 0th state represents an empty atom - a zero state.
Example:
{
"constraint": {
"CID": "C9",
"comment": "Constraint #9 - Days Off",
"penaltyVar": "hardPenalty",
"sumIter": "iterDim",
"iterDim": "R",
"iterVars": [ "r" ],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "T",
"iterVars": [ "t" ],
"exprMain": "ANY(r, t) * days_off(r, t)"
}
]
}
}
Fields if sumIter = iterArray
:
arrayName
: Required. The name of the array to iterate over.iterVars
: Required. Names the columns as variables coming from the set. So, the first variable is the first column in the set. In the example below,r, t, s, w
would correspond to the first four columns of the set.
Example:
{
"constraint": {
"CID": "C2",
"comment": "Constraint #2 - Origin and destination must be different",
"penaltyVar": "hardPenalty",
"sumIter": "iterArray",
"arrayName": "phi",
"iterVars": [
"r",
"s"
],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "S",
"iterVars": [
"i"
],
"exprMain": "Xr(r, i) * Xr(s, i)"
}
]
}
}
iterVar
vs. iterDim
?
You might be wondering what the difference is between:
{
"constraint": {
"sumIter": "iterVar",
"exprFrom": "0",
"exprTo": "T",
"iterVars": [ "t" ]
}
}
...and...
{
"constraint": {
"sumIter": "iterDim",
"iterDim": "T",
"iterVars": [ "t" ]
}
}
The answer would be theoretically nothing other than perhaps easier readability and less opportunity to make a typo. However, there are two very important reasons to use iterDim
instead of iterVar
when iterating over atom array dimensions:
- The solver does not have to evalute the end expression on each loop iteration - this can make a big difference in tight loops, especially when running in debug mode.
- When
iterDim
is used in the rootconstraint
'ssum
, the solver can optimize the recalculation of the solution score by only recalculating portions of constraints that have changed.
Thus, in short, use iterDim
for any and all complete atom array dimension sum
's.
params
This object defines the initialization settings for the atom array as well as the solver's mutator (how it alters the atom array during solving).
Example:
{
"params": {
"initializeType": "Random",
"mutatorType": "Random",
"maximize": false
}
}
Fields:
initializeType
: One of[Random|Unique]
:Random
: Initializes all atoms to random states.Unique
: Initializes all atoms to random states without duplicates. The number of states must match the number of atoms.
mutatorType
: One of[Swap|Random|Creep]
:Swap
: Only the order of the atoms can be changed.Random
: Any atom state can be randomly changed to any other.Creep
: Atom states are incremented or decremented only.
maximize
: One of[true|false]
: This defines whether the score for the problem is a maximization or minimization problem.
Expressions
Expressions are used in the problem definition (PD) within the following constraint
fields:
exprFrom
: The starting point for iteration in a constraint sum of typeiterateVar
.exprTo
: The ending point for iteration in a constraint sum of typeiterateVar
.exprMain
: The calculation that is added to the solution score or aresultVar
if defined.
Expressions are used to calculate a value and return it. They have no features for looping or control or functional decomposition - they are purely and simply expressions.
Expression Variables
By default, the following variables (constants actually) are available to all expressions:
R
: The R (resource count) dimension of the atom array.T
: The T (time count) dimension of the atom array.S
: The S (state count) dimension of the atom array.
Variables declared in the following places can also appear in expressions:
iterVars
: Variables declared in theiterVars
of a constraint sum.resultVar
: Result variable declared in a child constraint sum.constants
: Any constant define in theconstants
section of the PD.array
: Anyname
of anyarray
object in the PD.
Expression Operators
The following standard operators are supported in expressions. They are grouped in order of precedence:
!, NOT
: Not operators. (From 0 to 1 and vice versa.) These are the only unary operators.*, /, %
: Multiple, divide, modulo divide.+, -
: Add, subtract.<, <=, >, >=, =, !=
: Inequality operators. The result of these operators is either 1 or 0.AND, OR
: Boolean operators. The result of these operators is either 1 or 0.
Example:
53 / 100 * (2 + 1) > 1.55
Answer:
1.0
Expression Functions
There are several built-in functions and dynamically named array accessors that are supported in expressions. These fall into three categories:
- Functions that access the atom array:
A
andX
. - Inequality functions:
IQ
andEQ
. - Array accessor functions named by the array itself.
Expression Functions: Atom Array
As described in the Concepts section of this guide, the atom array can be accessed, or "viewed", as either atoms or bits. In addition, in cases where the atom array is composed of a single column or row, there are short hand functions for not having to specify the 1 dimension.
The following functions are used to access the atom array as atoms:
A(r, t)
: returns the atom state at resource r, time t.Ar(r)
: assuming there is only a single time column (T = 1
), then this will return the atom state atA(r, 0)
At(t)
: assuming there is only a single resource row (R = 1
), then this will return the atom state atA(0, t)
ANY(r, t)
: returns 1 or 0 (true or false) if there is a non-zero state at atomr, t
. This is used for atom matrices with zero states.
The following functions are used to access the atom array as bits:
X(r, t, s)
: returns 0 or 1 depending on whether bits
of the atom atr, t
is set (i.e., the atom state is equal tos
).Xr(r, s)
: assuming there is only a single time column (T = 1
), then this will return the bit state atX(r, 0, s)
Xt(t, s)
: assuming there is only a single resource row (R = 1
), then this will return the bit state atX(0, t, s)
Expression Functions: Inequality
The two inequality functions are extremely important to the solver. These are fundamentally different than the inequality operators in that they are used by the solver for more than just expression functions - they are used to identify inequalities for multivariable models. Also, while an inequality operator simply returns a 1
or 0
given a condition, the inequality functions return how unequal the variables being compared are, and thus implement the "unconstrained constraint" described in the concepts section.
The two fundamental inequality functions are EQ
and IQ
(for equality and inequality, respectively) however technically EQ
could be represented with an IQ
as will be shown shortly. These functions more closely match the declaration in a constraint description. For example, a constraint description that states "variable v must be between 2 and 10" would be captured with a IQ(2, v, 10)
expression.
EQ
and IQ
return a value that is typically multiplied by the penaltyVar
defined on the constraint. However, there are cases where the penalty value might be different if the function result is less than vs. greater than the comparison value. In these cases, two additional inequality functions, EQP
and IQP
are provided that take two additional parameters - the penalty if less than, and the penalty if greater than. In this case, the constraint would not have a penaltyVar
as the penalty is defined in the inequality.
-
EQ(v, n)
: Compares whether n and v are equal. Equality would return 0 while not equal would return the absolute value of the difference. Formally, this would be represented as:\( v = n\)
Therefore, if a constraint description states that some variable must be equal to a value, then this should be defined using the
EQ
function. -
IQ(n, v, m)
: Checks whetherv
is within (inclusively) the rangen
tom
, in which case 0 is returned, while outside the range would return the distance outside the range. Formally, this would be represented as:\( n \le v \le m\)
Note that either
n
orm
(but not both) can be the infinity symbol~
. For example, the expressionIQ(~, v, m)
would formally be:\( v \le m \)
Or, the expression
IQ(n, v, ~)
would be;\( v \ge n \)
In addition,
IQ
could implementEQ
with an expressionIQ(n, v, n)
, but this is for illustrative purposes only. -
EQP(v, n, pu, po)
: Same asEQ
but has two additional parameters to define the penalty under vs. over, respectively. -
IQP(n, v, m, pu, po)
: Same asIQ
but has two additional parameters to define the penalty under vs. over, respectively.
Expression Functions: Array Accessors
Arrays are accessed by their name and appear just like functions. In addition, their optional ldimFields
can be used as the last parameter. For example, given the array definition below:
{
"array": {
"name": "shift_on_req",
"file": "array_shift_on_req.json",
"ldimFields": [
"shift_on",
"shift_on_weight"
]
}
}
And given the follow sample array that this definition refers to:
[
[
[
0.0,
0.0
],
[
0.0,
0.0
]
]
]
An expression that accesses the array values could be:
(A(r, t) != shift_on_req(r, t, shift_on)) * shift_on_req(r, t, shift_on_weight)
Note that the last parameter of the array accessor refers to one of the ldimFields
. In this case, this array is in essence a 2D index into a set of records that each contain two fields.
File References
Array JSON files are referred to from the problem definition (PD) via the array
, and sometimes, atoms
objects. The array
object refers to just the array while while an atoms
reference also includes atom array configuration (resourceCount, timeCount and stateCount). The design behind having these as external resources (as opposed to embedded in the PD) is so that dynamic parts of a problem definition can be updated (or created) while the core constraints within the PD do not need to change. In other words, this allows the PD file to remain fixed, and only the data (input) files changed.
For example, if an employee schedule is being created, there may be different numbers of employees each week or pay period. However, this wouldn't change the constraints - but it would change the number of resources in the atom array. A traveling salesperson problem may have a different number of locations each run, but again, the constraint definitions wouldn't change.
Array JSON File Format
array
objects within the PD refer to a JSON file that has a JSON array format. As shown in the below example of a two dimensional array:
[
[ 3360.0, 4320.0, 2.0, 5.0, 2.0, 1.0 ],
[ 3360.0, 4320.0, 2.0, 5.0, 2.0, 1.0 ],
[ 3360.0, 4320.0, 2.0, 5.0, 2.0, 1.0 ],
[ 3360.0, 4320.0, 2.0, 5.0, 2.0, 1.0 ],
[ 3360.0, 4320.0, 2.0, 5.0, 2.0, 1.0 ],
[ 3360.0, 4320.0, 2.0, 5.0, 2.0, 1.0 ],
[ 3360.0, 4320.0, 2.0, 5.0, 2.0, 1.0 ],
[ 3360.0, 4320.0, 2.0, 5.0, 2.0, 1.0 ]
]
In Python, the creation of an array as shown above is trivially:
import numpy
import json
na = numpy.zeros((8, 6))
# ...(fill in array a with values)...
a = na.tolist()
with open("filename.json", "w") as f:
json.dump(a, f, indent=4)
Atoms JSON File Format
The atoms JSON file format is simply a separate JSON file that is in the same format as the atoms
object in the PD. For example:
{
"atoms": {
"resourceCount": 4,
"timeCount": 4,
"stateCount": 4
}
}
The atoms
object also contains an array
field which contains an array as defined in the previous section. For example:
{
"atoms": {
"resourceCount": 4,
"timeCount": 4,
"stateCount": 4,
"array": [
[ 0, 0, 1, 0 ],
[ 2, 0, 0, 0 ],
[ 0, 3, 3, 0 ],
[ 2, 0, 0, 1 ]
]
}
}
Note that when the array
is specified within the atoms
object, it must be a two dimensional array with the number of rows equal to resourceCount
and columns equal to timeCount
and the elements (which become atoms) must be less than stateCount
.
Internet Usage
This information is provided for IT Administrators when configuring network security.
Internet access is required for licensing and for quantum random seeding. All requests are REST requests to the renniedatascience.com
domain. Following are specific configuration descriptions:
HTTPS GET
REST requests are made to the domainrenniedatascience.com
- Headers include:
- Local license key contained within the
license.txt
file in the Amazolve bin directory.
- Local license key contained within the
- Agent:
Amazolve/(client version)
- No request content is set/sent.
Common Parameters
Whether using the command line (CLI) or Python interfaces, the solver parameters are for the most part the same. These are described below:
-
Problem definition file
-
The full path to the problem definition (PD) JSON file.
-
CLI:
--pf
, Python:problem_file
-
-
External file data path
-
The full path to the directory that contains all external files (e.g., sets, matrices, atoms) used by the PD. Just the file names (not full path) are referenced in the PD so this must be set here.
-
CLI:
--dp
, Python:data_path
-
-
NVIDIA Device
-
The index of the NVIDIA device, typically 0 for a PC with a single GPU.
-
CLI:
--device
, Python:device
-
-
Run mode
-
Whether to run in debug or release mode. All problems should be tested in debug mode as debug mode examines all variables, logic errors, etc. but it is MUCH slower as it does not run on the GPU. Thus it is best to test small samples in debug mode. In release mode, it is run on the GPU and will not offer debugging information and thus may just crash.
-
CLI:
--run debug|release
, Python:debug=true|false
-
-
Thread count
-
The number of threads to utilize. In debug mode, these threads are run in serial - i.e., they are not true threads. In release mode of course they are. This should be set based on the capabilities of the GPU and the memory requirements of the problem.
-
CLI:
--tc
, Python:thread_count
-
-
Stop seconds
-
The maximum number of seconds to run the solver.
-
CLI:
--st
, Python:stop_seconds
-
-
Stop score
-
Stop the solver when it reaches this score.
-
CLI:
--ss
, Python:stop_score
-
-
Stop if no change
-
Stop the solver when the score does not improve after this many seconds.
-
CLI:
--sn
, Python:stop_no_change
-
AZO CLI
The command line interface is available via the azo.exe
executable located in the Amazolve installation directory (where you unzipped the release). Command line parameters are given in the previous Common Parameters section. Below is an example:
azo --run release \
--pf "c:\Amazolve\samples\nurseProb\nurse.json" \
--dp "c:\Amazolve\samples\nurseProb\dyndata\1\" \
--tc 1536 --ss 607 --st 120
Python Interface
In order to use the Python interface, two paths must be defined so Python apps can access both the CUDA toolkit as well as the Amazolve installation directory where the azopy.pyd module resides. To add the CUDA toolkit path, use the following Python code near the top of the file (see full sample below):
import os
os.add_dll_directory(os.environ['CUDA_PATH'] + r"\bin")
Then, add the Amazolve installation directory to the PYTHONPATH:
set PYTHONPATH=%PYTHONPATH%;C:\Amazolve\bin
Objects
The Python interface to Amazolve contains the following objects:
Solver
: Runs the solver.SolverConfig
: Contains all the fields described in the previous Common Parameters section.SolverResult
: Contains the results of the solver run.ConstraintResult
: A list of these is contained inSolverResult
.
Solver Object
- Methods:
- `SolverResult solve(SolverConfig)'
- Returns:
SolverResult
(object) - Parameters:
SolverConfig
(object)
- Returns:
- `SolverResult solve(SolverConfig)'
SolverConfig Object
- Fields:
- (Contains all fields listed in Common Parameters)
SolverResult Object
- Fields:
resource_count
: Returns the configured resource count of the atom matrix.time_count
: Returns the configured time count of the atom matrix.state_count
: Returns the configured state count of the atom matrix.constraints
: A list ofConstraintResult
objects containing individual constraint scores.score
: The final score of the returned atoms.atoms
: A 2d array of the atoms.stop_reason
: The reason the solver stopped:0
: Unknown reason, likely a failure.1
: Target score was reached.2
: Total run time was reached.3
: No change in score time was reached.
time_taken
: The total time taken (in seconds).
ConstraintResult Object
- Fields:
cid
: The constraint ID (CID
) defined in the constraint in the problem definition.cscore
: The constraint's score.ctime
: The time (in milliseconds) the constraint took. This is only provided in debug run mode.
Examples Repo
A complete Python test application is available here.
Gas
This example is a multivariable maximization problem, thus the variables (atom states) are continuous in nature. The full details of the problem are described and fully attributed to here.
In this case, the atom array is defined as follows:
- 2 resource rows:
- row 1: level of gas
- row 2: level of chloride
- 1 time column
- 100 states: floating point value from 0.00 - 0.99 (quantized interval of 0.01)
Next is a sample run via the azo
command line utility (CLI):
azo command line parameters:
--run debug --pf "C:\Amazolve\samples\gasProb\gas.json" --tc 10 --st 10 --ss 2300
Output:
***** Score: 2300.000000 in 0.395000 seconds *****
***** Score Reached *****
** Solver Results **
--------------------
Score : 2300.000000
Stop : 1
Time : 0.395000
R Count: 2
T Count: 1
S Count: 100
Atoms:
[
[ 20 ],
[ 30 ]
]
Constraints:
S1: 2300.000000 (6 ms)
ctMaxTotal: -0.000000 (5 ms)
ctMaxTotal2: -0.000000 (8 ms)
ctMaxChloride: -0.000000 (6 ms)
Traveling Salesperson Problem (TSP)
The traveling salesperson problem (TSP) is a classic combinatorial problem where a salesperson is asked to visit a certain number of cities and would like to find the shortest route while visiting each. The atom array in this case is a single resource row (the salesperson) and a time column for each step of the route. The atom state is the city that is to be visited at time t, so it is obvious that each atom state must be unique - the salesperson doesn't want to visit the same place twice.
This is accomplished by setting the following problem definition (tsp.json
) parameters as shown below:
{
"params": {
"initializeType": "Unique",
"mutatorType": "Swap"
}
}
Thus, all atom states are initialized randomly, but each is required to be a unique state - implying that the number of states must match the number of atoms. And, the only mutator type allows is "Swap" which means the states will remain unique.
In the samples directory:
tspProb
contains the problem definition (tsp.json
) and a subdirectory calleddyndata
which contains a random cost array that contains the distances between each city.- In the Python sample tester (azopytest), the tsp directory contains the python code that generates the random cost array.
Next is a sample run via azopytest.
Python sample tester (azopytest) command line parameters:
--run debug --itest tsp --tc 5 --st 20
Output:
** Problem Results **
---------------------
** Solver Results **
--------------------
Score : 6.789317607879639
Stop : 2
Time : 20.376
R Count: 1
T Count: 100
S Count: 100
Atoms:
[
[46, 59, 83, 53, 11, 17, 64, 75, 26, 39, 15, 51, 66, 36, 23, 47, 14, 85, 87, 44, 5, 25, 69, 16, 29, 3, 81, 35, 63, 28, 93, 88, 65, 55, 90, 31, 61, 1, 57, 73, 32, 10, 0, 4, 22, 21, 80, 89, 95, 99, 40, 24, 84, 30, 67, 96, 37, 92, 74, 72, 86, 98, 34, 54, 91, 76, 94, 27, 58, 60, 71, 62, 70, 50, 8, 19, 52, 49, 42, 41, 7, 20, 82, 77, 38, 12, 13, 18, 79, 2, 48, 45, 68, 56, 97, 9, 6, 33, 43, 78]
]
Constraints:
O1: 6.789317607879639 (19732 ms)
Nurse Rostering
The Nurse Rostering sample is based on the benchmark information as well as public data sets referred to here. The sample defines an atom array where rows correspond to nurses, columns are days, and atom states are shift types. There are numerous constraints and objectives, all described in the aforementioned benchmark paper. Thus, it is recommended the paper be used as a guide to understanding the sample.
In the samples directory:
nurseDB
contains five instances of raw CSV data files that describe the number of nurses, days, etc. These would be generated by an upstream HR system, for example.nurseProb
contains the problem definition (nurse.json
) and a subdirectory calleddyndata
which contains the CSV data converted into arrays that are referred to by the problem definition.- In the Python sample tester (azopytest), the nurse directory contains the python code for converting the raw data files into the arrays.
Next is a sample run via azopytest.
Python sample tester (azopytest) command line parameters:
--run release --itest nurse --tc 1536 --ss 607 --st 60 --nurseinst 3
Output:
** Problem Results **
---------------------
A: DDD DDD DD
B: DDDD DL LLL
C: L EL EED LL
D: EL EEL LL
E: EEE EEE EE
F: DDDD EEE ED
G: LLL DL EEE
H: E EELL LLL
I: EDL EL ED
J: DD DDDD DD
K: EDD DDDDDD
L: LLL DDDDDD
M: EE EEDLLL
N: DDDLLL DD
O: LL DDDLL
P: DDD L
Q: L L DD
R: D ED E
S: EEEE
T: E DD D
** Solver Results **
--------------------
Score : 1442.0
Stop : 2
Time : 60.975
R Count: 20
T Count: 14
S Count: 4
Atoms:
[
[0, 0, 2, 2, 2, 0, 0, 2, 2, 2, 0, 0, 2, 2],
[0, 2, 2, 2, 2, 0, 0, 2, 3, 0, 0, 3, 3, 3],
[3, 0, 0, 1, 3, 0, 0, 1, 1, 2, 0, 0, 3, 3],
[0, 1, 3, 0, 0, 1, 1, 3, 0, 0, 3, 3, 0, 0],
[1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0],
[0, 2, 2, 2, 2, 0, 0, 1, 1, 1, 0, 0, 1, 2],
[3, 3, 3, 0, 0, 2, 3, 0, 0, 1, 1, 1, 0, 0],
[1, 0, 0, 1, 1, 3, 3, 0, 0, 3, 3, 3, 0, 0],
[0, 0, 1, 2, 3, 0, 0, 0, 1, 3, 0, 0, 1, 2],
[2, 2, 0, 0, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0],
[0, 1, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0],
[0, 3, 3, 3, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2],
[0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 2, 3, 3, 3],
[2, 2, 2, 3, 3, 3, 0, 0, 0, 2, 2, 0, 0, 0],
[3, 3, 0, 0, 0, 2, 2, 2, 3, 3, 0, 0, 0, 0],
[0, 0, 0, 0, 2, 2, 2, 0, 0, 3, 0, 0, 0, 0],
[0, 0, 0, 0, 3, 0, 0, 3, 0, 0, 0, 0, 2, 2],
[2, 0, 0, 0, 0, 1, 2, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0]
]
Constraints:
C2: 0.0 (0 ms)
C3: 0.0 (0 ms)
C4: 0.0 (0 ms)
C5: 0.0 (0 ms)
C6: 0.0 (0 ms)
C7: 0.0 (0 ms)
C8: 0.0 (0 ms)
C9: 0.0 (0 ms)
O1: 39.0 (0 ms)
O2: 2.0 (0 ms)
O3: 1401.0 (0 ms)
Supply Chain (PBS)
In this sample, a manufacturer wishes to minimize emissions in the transport of sub-parts to manufacturing of composite parts within it's supply-chain. The manufacturer has warehouses or manufacturing centers in various locations and there are known costs associated with moving different parts to different locations. In addition, a "Part Breakdown Structure" (PBS) describes which parts are components of other parts.
The atom array this case is a resource row for each part, a single time column and each atom state is a location.
Below is the PBS provided in the sample (in the pbs.csv
raw data file). Parts P2 and P3 are sub-parts of P1, and P4, P5, and P6 are considered "sibling" parts.
Therefore:
- The constraints involved include the PBS and rules around that structure.
- The objective is to minimize the emissions cost described in a table mapping parts to source and destination locations.
Please see the compliance section for a complete description of the problem, constraints and objectives in both mathematical and Amazolve forms. In addition, the pbs.md
file in the sample pbsProb
directory has the same information.
In the samples directory:
pbsDB
contains raw CSV data files that describe the PBS (pbs.csv
) and the cost table (costs.csv
). These would be generated by an upstream ERP system, for example.pbsProb
contains the problem definition (pbs.json
andpbs.md
) and a subdirectory calleddyndata
which contains the CSV data converted into arrays that are referred to by the problem definition.- In the Python sample tester (azopytest), the pbs directory contains the python code for converting the raw data files into the arrays.
Next is a sample run via azopytest.
Python sample tester (azopytest) command line parameters:
--run debug --itest pbs --tc 5 --st 20
Output:
** Problem Results **
---------------------
P1: S2
P2: S4
P5: S6
P6: S2
P7: S3
P3: S3
P8: S2
P4: S6
P9: S2
P10: S4
** Solver Results **
--------------------
Score : 8.389999389648438
Stop : 2
Time : 20.273
R Count: 10
T Count: 1
S Count: 7
Atoms:
[
[1],
[3],
[2],
[5],
[5],
[1],
[2],
[1],
[1],
[3]
]
Constraints:
C1: 0.0 (26 ms)
C2: 0.0 (316 ms)
C3: 0.0 (249 ms)
C: 8.389999389648438 (3433 ms)