Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
frankhuettner committed Feb 7, 2022
0 parents commit 87bb2c4
Show file tree
Hide file tree
Showing 20 changed files with 1,242 additions and 0 deletions.
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: CI
on:
pull_request:
branches:
- main # Rename master
push:
branches:
- main
tags: '*'
jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- '1.7.1' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'.
- '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia.
- 'nightly'
os:
- ubuntu-latest
arch:
- x64
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: actions/cache@v1
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v1
with:
file: lcov.info
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
with:
version: '1'
- run: |
julia --project=docs -e '
using Pkg
Pkg.develop(PackageSpec(path=pwd()))
Pkg.instantiate()'
- run: |
julia --project=docs -e '
using Documenter: doctest
using NewsvendorModel
doctest(NewsvendorModel)'
- run: julia --project=docs docs/make.jl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tmp/

docs/build
docs/site
.Rhistory
Manifest.toml
.vscode
21 changes: 21 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2022 Frank Huettner

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
15 changes: 15 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name = "NewsvendorModel"
uuid = "63d3702b-073a-45e6-b43c-f47e8b08b809"
authors = ["frankhuettner <[email protected]>"]
version = "0.1.0"

[deps]
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
102 changes: 102 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
[![](https://img.shields.io/badge/docs-dev-blue.svg)](https://frankhuettner.github.io/NewsvendorModel.jl/dev/)
[![CI](https://github.com/frankhuettner/NewsvendorModel.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/frankhuettner/NewsvendorModel.jl/actions/workflows/ci.yml)
[![Coverage](https://codecov.io/gh/FrankHuettner/NewsvendorModel.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/FrankHuettner/NewsvendorModel.jl)


# NewsvendorModel.jl


This is a lightweight and simple Julia package for modeling and solving [newsvendor problems](https://en.wikipedia.org/wiki/Newsvendor_model).

## Setup

NewsvendorModel.jl requires an installation of Julia (can be downloaded from the [official website](https://julialang.org/)). You can install NewsvendorModel.jl like any other Julia package using the REPL as follows:


```julia
julia> import Pkg
julia> Pkg.add(url="https://github.com/frankhuettner/NewsvendorModel.jl")
```
After installation, it can be loaded with the usual command.
```julia
julia> using NewsvendorModel
```

Moreover, you need to load the Distributions.jl package.
```julia
julia> using Distributions
```

## Usage

1. Define a model with the function `nvm = NVModel(cost, price, demand)` using the following required arguments:
- unit production `cost`
- unit selling `price`
- `demand` distribution, which can be any choosen from the [Distributions.jl](https://juliastats.org/Distributions.jl/latest/univariate/) package
2. Solve for optimal quanitity and obtain key metrics with the `solve(nvm)` function.

Note that additional keyword arguments can be passed in *Step 1*: `salvage` value,
`backlog`, `fixcost`, `q_min`, and `q_max`. Moreover, it is possible to obtain the unrounded optimal quantity by passing `rounded=false` in *Step 2*. For more details go to [the documentation](https://frankhuettner.github.io/NewsvendorModel.jl/dev/x2_options/).


## Example

Consider an [example](https://en.wikipedia.org/wiki/Newsvendor_model#Numerical_examples) with
- unit `cost` = 5
- unit `price` = 7
- `demand` that draws from a normal distribution with
- mean = 50
- standard deviation = 20

Define the model and store it in the variable `nvm` as follows:

```julia
julia> nvm = NVModel(5, 7, Normal(50, 20))
```

Julia shows the model data:
```julia
Data of the Newsvendor Model
* Unit cost: 5.00
* Unit selling price: 7.00
* Demand distribution: Normal{Float64}=50.0, σ=20.0)
```

Next, you can solve the model and store the result in the variable `res` like so:
```julia
julia> res = solve(nvm)
```
This gives the following output:
```julia
=====================================
Results of maximizing expected profit
* Optimal quantity: 39 units
* Expected profit: 52.69
=====================================
This is a consequence of
* Cost of underage: 2.00
* Cost of ovderage: 5.00
* The critical fractile: 0.29
* Rounded to closest integer: true
-------------------------------------
Ordering the optimal quantity yields
* Expected sales: 35.38 units
* Expected lost sales: 14.62 units
* Expected leftover: 3.62 units
* Expected backlog penalty: 0.00
-------------------------------------
```
Moreover, you have stored the result in the varial `res`. Reading the data from the stored result is straight-forward:
```julia
julia> q_opt(res)
39
```

```julia
julia> profit(res)
52.687735385066865
```

Analogously, `underage_cost(res)`, `overage_cost(res)`, `critical_fractile(res)`,
`rounded(res)`, `sales(res)`, `lost_sales(res)`, `leftover(res)`, `penalty(res)`,
read the other information the stored in `res`. The model that was solved can be retrieved with `nvmodel(res)`.
5 changes: 5 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"

[compat]
Documenter = "0.27"
13 changes: 13 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Documenter
using NewsvendorModel

makedocs(
sitename = "NewsvendorModel.jl",
format = Documenter.HTML(),
modules = [NewsvendorModel]
)

deploydocs(
repo = "github.com/frankhuettner/NewsvendorModel.jl.git",
devbranch = "main",
)
Binary file added docs/src/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 106 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Quick start

This is a lightweight and simple Julia package for modeling and solving [newsvendor problems](https://en.wikipedia.org/wiki/Newsvendor_model).

## Setup

NewsvendorModel.jl requires an installation of Julia (can be downloaded from the [official website](https://julialang.org/)). You can install NewsvendorModel.jl like any other Julia package using the REPL as follows:


```julia
julia> import Pkg
julia> Pkg.add(url="https://github.com/frankhuettner/NewsvendorModel.jl")
```
After installation, it can be loaded with the usual command.
```julia
julia> using NewsvendorModel
```

Moreover, you need to load the Distributions.jl package.
```julia
julia> using Distributions
```

## Usage

1. Define a model with the function `nvm = NVModel(cost, price, demand)` using the following required arguments:
- unit production `cost`
- unit selling `price`
- `demand` distribution, which can be any choosen from the [Distributions.jl](https://juliastats.org/Distributions.jl/latest/univariate/) package
2. Find the optimal quanitity and obtain key metrics:
- `q_opt(nvm)` returns the quantity that maximizes the expected profit
- `profit(nvm, q)` to get the expected profit if `q` is stocked
- `profit(nvm)` is short for the maximal expected profit `profit(nvm, q_opt(nvm))`
- `solve(nvm)` gives a list of further important metrics (critical fractile as well as expected sales, lost sales, and leftover).

Additional keyword arguments specifying salvage value, backlog, fixcost, q_min, and q_max can be passed in *Step 1*. To obtain the unrounded optimal quantity, pass `rounded=false` in *Step 2*.



## Example

Consider an [example](https://en.wikipedia.org/wiki/Newsvendor_model#Numerical_examples) with
- unit `cost` = 5
- unit `price` = 7
- `demand` that draws from a normal distribution with
- mean = 50
- standard deviation = 20

Define the model and store it in the variable `nvm` as follows:

```julia
julia> nvm = NVModel(5, 7, Normal(50, 20))
```

Julia shows the model data:
```julia
Data of the Newsvendor Model
* Unit cost: 5.00
* Unit selling price: 7.00
* Demand distribution: Normal{Float64}=50.0, σ=20.0)
```

Next, you can solve the model and store the result in the variable `res` like so:
```julia
julia> res = solve(nvm)
```
This gives the following output:
```julia
=====================================
Results of maximizing expected profit
* Optimal quantity: 39 units
* Expected profit: 52.41
=====================================
This is a consequence of
* Cost of underage: 2.00
* Cost of overage: 5.00
* The critical fractile: 0.29
* Rounded to closest integer: true
-------------------------------------
Ordering the optimal quantity yields
* Expected sales: 35.34 units
* Expected lost sales: 14.66 units
* Expected leftover: 3.66 units
-------------------------------------
```
Moreover, you have stored the result in the variable `res`. Reading the data from the stored result is straight-forward:
```julia
julia> q_opt(res)
39
```

```julia
julia> profit(res)
52.40715617998893
```

Analogously, `underage_cost(res)`, `overage_cost(res)`, `critical_fractile(res)`, `sales(res)`, `lost_sales(res)`, `leftover(res)` read.

Applying the above functions directly to the model (instead of to the result) is also possible, with the ability to pass a quantity. For instance,

```julia
julia> leftover(nvm, 39)
3.656120545715868
```

An advantage of using the `solve` function and then reading the data from the result lies in the fact that the model that was solved can be retrieved with `nvmodel(res)`.
40 changes: 40 additions & 0 deletions docs/src/x20_model_options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Further options for specifiying the model

You can pass the following optional keyword arguments in the *definition* of a newsvendor model `NVModel(cost, price, demand [; kwargs])`:

- `salvage` value obtained from scraping a leftover unit; might be negative, e.g., due to disposal cost, extra captial cost, or warehousing cost; defaults to 0
- `backlog` penalty for being short a unit, e.g., contractual penalty for missing delivery targets or missed future profit of an unserved customer; defaults to 0
- `fixcost` fixed cost of operations; defaults to 0
- `q_min` minimal feasible quantity, e.g., due to production limits; must be nonnegative; defaults to 0
- `q_max` maximal feasible quantity, e.g., due to production limits; must be greater than or equal to `q_min`; defaults to `Inf`inity



## Example

Define a newsvendor problem with unit cost 5, unit price 7, normal demand
around 50 with standard deviation 20, where a unit salvages for 0.5, and back order incurs at a penalty of 2 per unit, as follows:

```julia
julia> nvm2 = NVModel(cost = 5, price = 7, demand = Normal(50, 20), salvage = 0.5, backlog = 2)
Data of the Newsvendor Model
* Unit cost: 5.00
* Unit selling price: 7.00
* Demand distribution: Normal{Float64}=50.0, σ=20.0)
* Unit salvage value: 0.50
* Unit backlog penalty: 2.00
```

Note that cost, price, and demand are necessary arguments that can be passed without keyword. Moreover, only values that differ from the default value will be shown in the REPL. For instance, adding `q_min=0.0` does not have an impact.

```julia
julia> nvm3 = NVModel(5, 7, Normal(50, 20), salvage = 0.5, backlog = 2, q_min=0.0)
Data of the Newsvendor Model
* Unit cost: 5.00
* Unit selling price: 7.00
* Demand distribution: Normal{Float64}=50.0, σ=20.0)
* Unit salvage value: 0.50
* Unit backlog penalty: 2.00
julia> nvm3 == nvm2
true
```
Loading

2 comments on commit 87bb2c4

@frankhuettner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register branch=main

Release notes:

This is a lightweight and simple Julia package for modeling and solving newsvendor problems.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/54067

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.1.0 -m "<description of version>" 87bb2c40371a4494c4561c05a825caacd515260d
git push origin v0.1.0

Please sign in to comment.