diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..3039704
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,16 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+updates:
+ - package-ecosystem: "github-actions" # See documentation for possible values
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "weekly"
+
+ - package-ecosystem: "nuget"
+ directory: "/src"
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..7b3f033
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,42 @@
+name: "Publish package"
+on:
+ push:
+ tags:
+ - "v[0-9]+.[0-9]+.[0-9]+"
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET SDKs
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 6.0.x
+ 8.0.x
+
+ - name: Verify commit exists in origin/main
+ run: |
+ git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
+ git branch --remote --contains | grep origin/main
+
+ - name: Set VERSION variable from tag
+ run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV
+
+ - name: Build
+ run: dotnet build --configuration Release /p:Version=${VERSION}
+
+ - name: Test
+ run: dotnet test --configuration Release /p:Version=${VERSION} --no-build
+
+ - name: Pack
+ run: dotnet pack --configuration Release /p:Version=${VERSION} --no-build --output .
+
+ - name: Push
+ run: dotnet nuget push Anexia.MathematicalProgram.${VERSION}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${NUGET_TOKEN}
+ env:
+ NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..3ae50b8
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,32 @@
+name: "Run tests"
+
+on: [ push, pull_request ]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET SDKs
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 6.0.x
+ 8.0.x
+
+ - name: Dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build --configuration Release
+
+ - name: Test
+ run: dotnet test --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
+
+ - uses: codecov/codecov-action@v5
+ with:
+ fail_ci_if_error: true # optional (default = false)
+ verbose: true # optional (default = false)
+ token: ${{ secrets.CODECOV_TOKEN }}
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..59a96a7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,45 @@
+# Common IntelliJ Platform excludes
+
+# User specific
+**/.idea/**/workspace.xml
+**/.idea/**/tasks.xml
+**/.idea/shelf/*
+**/.idea/dictionaries
+target/
+
+# Sensitive or high-churn files
+**/.idea/**/dataSources/
+**/.idea/**/dataSources.ids
+**/.idea/**/dataSources.xml
+**/.idea/**/dataSources.local.xml
+**/.idea/**/sqlDataSources.xml
+**/.idea/**/dynamic.xml
+
+# Rider
+# Rider auto-generates .iml files, and contentModel.xml
+**/.idea/**/*.iml
+**/.idea/**/contentModel.xml
+**/.idea/**/modules.xml
+
+**/.idea/
+.tmp/
+.teamcity/
+
+# Ignore http client files from rider
+**/.http
+*.http
+
+*.suo
+*.user
+.vs/
+[Bb]in/
+[Oo]bj/
+_UpgradeReport_Files/
+[Pp]ackages/
+
+Thumbs.db
+Desktop.ini
+.DS_Store
+**/logs/
+/**/**/obj/
+/**/**/bin/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..f24c243
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,15 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v3.2.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-yaml
+ - id: check-added-large-files
+
+- repo: https://github.com/dotnet/format
+ rev: v8.0.453106
+ hooks:
+ - id: dotnet-format
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..e69de29
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..2d22982
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,31 @@
+# Guidance on how to contribute
+
+> By submitting a pull request or filing a bug, issue, or feature request,
+> you are agreeing to comply with this waiver of copyright interest.
+> Details can be found in our [LICENSE](LICENSE).
+
+
+There are two primary ways to help:
+- Using the issue tracker, and
+- Changing the code-base.
+
+
+## Using the issue tracker
+
+Use the issue tracker to suggest feature requests, report bugs, and ask questions.
+This is also a great way to connect with the developers of the project as well
+as others who are interested in this solution.
+
+Use the issue tracker to find ways to contribute. Find a bug or a feature, mention in
+the issue that you will take on that effort, then follow the _Changing the code-base_
+guidance below.
+
+
+## Changing the code-base
+
+Generally speaking, you should fork this repository, make changes in your
+own fork, and then submit a pull request. All new code should have associated
+unit tests that validate implemented features and the presence or lack of defects.
+Additionally, the code should follow any stylistic and architectural guidelines
+prescribed by the project. In the absence of such guidelines, mimic the styles
+and patterns in the existing code-base.
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..faf586e
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,46 @@
+
+
+ net8.0
+ enable
+ 12
+ true
+ true
+ latest
+ false
+ false
+ true
+ CA1716
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fb4b56a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 ANEXIA Internetdienstleisungs GmbH
+
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fd99bda
--- /dev/null
+++ b/README.md
@@ -0,0 +1,168 @@
+# dotnet-mathematical-program
+
+[![](https://img.shields.io/nuget/v/Anexia.MathematicalProgram "NuGet version badge")](https://www.nuget.org/packages/Anexia.MathematicalProgram)
+[![](https://github.com/anexia/dotnetcore-mathematical-program/actions/workflows/test.yml/badge.svg?branch=main "Test status")](https://github.com/anexia/dotnetcore-mathematical-program/actions/workflows/test.yml)
+[![codecov.io](https://codecov.io/github/Anexia/dotnetcore-mathematical-program/coverage.svg?branch=main "Code coverage")](https://codecov.io/github/Anexia/dotnetcore-mathematical-program/coverage.svg?branch=main)
+This library allows you to build and solve linear programs and integer linear programs in a very handy way.
+The implementation uses Google´s GLOP linear solver for linear programs and optionally, the Coin-OR CBC branch and cut
+solver
+or the Gurobi solver for integer linear programs via the [Google OR-Tools](https://developers.google.com/optimization)
+API.
+
+## Installation
+
+- Install the latest version of `Anexia.MathematicalProgram` package via nuget
+
+## Description
+
+This library works for any linear program (LP) or integer linear program (ILP).
+
+### Anexia.MathematicalProgram.Model
+
+- To build the objective function of your LP/ILP you can use the class `Terms`.
+ Each `Term` is defined by a `Coefficient` and a variable of type `Google.OrTools.LinearSolver.Variable`.
+ Moreover, there is the possibility to have an additional `Constant`.
+
+- To build your constraints you can use the class `Constraints`.
+ Each `Constraint` is defined by `Terms` and an `Interval` that has a lower and an upper bound of
+ type `double`.
+ For binary intervals simply use `Interval.BinaryInterval`.
+ Another implementation is the class `Point` for the case of lower bound equals upper bound.
+
+### Anexia.MathematicalProgram.Solve
+
+#### Linear Programming
+
+For solving an LP you may initialize the `LinearProgramSolver` which uses the GLOP solver in the background.
+
+- **Configuration:** Via `LinearProgramSolver.SetSolverConfigurations()` you can set `SolverParameter` containing a `TimeLimitInMilliseconds`,
+ the `NumberOfThreads` that should be maximally used, a `EnableSolverOutput` flag to determine if the solver output should be
+ printed on the console and the `RelativeGap` to specify the gap where the solver terminates.
+- **Variables:** Via `LinearProgramSolver.AddContinuousVariable()` your continuous LP variables can be added
+ using an `IInterval` and a variable name of type `string`.
+ The `out`parameter of this method is of type `Google.OrTools.LinearSolver.Variable`.
+- **Constraints:** Via `LinearProgramSolver.AddConstraints()` you can simply add your beforehand initialized
+ contraints to the solver.
+- **Objective:** Via `LinearProgramSolver.AddObjective()` you can add your objective in form of the `Terms`
+ and a `Constant` to the solver.
+ With a `bool` you can choose if the LP should be `minimized` or `maximized`.
+- **Solve:** Via `LinearProgramSolver.Solve()` you either
+ - obtain a `SolverResult` (explained below) or
+ - a `MathematicalProgramException` with a detailed message on the occured problem is thrown.
+
+#### Integer Linear Programming
+
+For solving an ILP you may initialize the `IntegerLinearProgramSolver` which uses per default the CBC solver in the
+background.
+Using the highly performant Gurobi solver requires a valid license.
+Creating a solver using Gurobi can be done in two ways. Either by passing the argument
+`IntegerLinearProgramSolver(ILPSolverType.GurobiMixedIntegerProgramming)`,
+or using the static
+method `IntegerLinearProgramSolver.Create(ILPSolverType.GurobiMixedIntegerProgramming, our var message)`.
+In both ways, the solver checks if the given type is supported, e.g., a valid licence is present, or otherwise, creates
+the solver of type CBC. The `Create` method additionally returns a warning message via out parameter.
+If the solver has been created as expected, this message is null, otherwise, it contains information that the
+solver type switched to CBC.
+
+The main difference to linear program solving is that in this case just integer variables can be added.
+The rest of the methods work similar to the LinearProgramSolver.
+
+- **Variables:** Via `IntegerLinearProgramSolver.AddIntegerVariable()` your integer ILP variables can be added using
+ an `IInterval` and a variable name of type `string`. The `out`parameter of this method is of type
+ `Google.OrTools.LinearSolver.Variable` which are strictly integer.
+- **Configuration**, **Constraints**, **Objective**, and **Solve** as above.
+
+### Anexia.MathematicalProgram.Result
+
+After solving the LP/ILP you get a `SolverResult` according to the `Google.OrTools.LinearSolver.Solver.ResultStatus`.
+The `SolverResult` containts following information:
+
+- **Solver:** This is the already solved `Google.OrTools.LinearSolver.Solver`.
+ - You have the opportunity to log the LP/ILP model in a human readable format by `Solver.ExportModelAsLpFormat()`.
+ - You can read out the actual values of the variables via `Google.OrTools.LinearSolver.Variable.SolutionValue()`
+ to transform the result correctly.
+ - As soon as the solved solver is not needed any more, it should be removed via `Solver.Dispose()`.
+- **ObjectiveValue:** Actual objective value. This value can be either the optimum, a deviation of the optimum
+ if the LP/ILP was not entirely solved, or `double.NaN` if the LP/ILP is infeasible.
+- **IsFeasible:** Information whether the LP/ILP is generally feasible.
+- **IsOptimal:** Information wheter the LP/ILP was solved to optimality.
+- **OptimalityGap:** The deviation to the optimum calculated by
+ `Math.Abs(objective.BestBound() - objectiveValue) / objectiveValue)`. This value is `0` if the optimum was reached,
+ and `double.NaN` if the model is infeasible.
+
+***
+
+## Examples for using this library
+
+### Example 1 (Build and solve LP)
+
+- Feasible model: max x, s.t. x <= 2, x >= 1, continuous variable x <= 5
+- Result: x = 2, objective value = 2
+
+```
+var solver = new LinearProgramSolver().SetSolverConfigurations(TimeLimitInMilliseconds.Unbounded);
+
+solver = solver.AddContinuousVariable(new Interval(double.NegativeInfinity, 5), "TestVariable", out var testVariable);
+
+solver = solver.AddObjective(new Terms(new Term(new Coefficient(1), testVariable)), false);
+
+var constraints = new Constraints(
+ new Constraint(new Terms(new Term(new Coefficient(1), testVariable)),
+ new Interval(double.NegativeInfinity, 2)),
+ new Constraint(new Terms(new Term(new Coefficient(1), testVariable)),
+ new Interval(1, double.PositiveInfinity)));
+
+solver = solver.AddConstraints(constraints);
+
+var result = solver.Solve();
+
+Logger.Information(result.SolvedSolver.ExportModelAsLpFormat(false));
+```
+
+### Example 2 (Build and solve ILP)
+
+- Feasible model: min 2x + y, s.t. x >= y, integer variables x in [1,3], y binary
+- Result: x = 1, y = 0, objective value = 2
+
+```
+var solver = new IntegerLinearProgramSolver()
+ .SetSolverConfigurations(new TimeLimitInMilliseconds(10), 2, true)
+ .AddIntegerVariable(new Interval(1, 3), "VariableX", out var variableX)
+ .AddIntegerVariable(new Interval(0, 1), "VariableY", out var variableY)
+ .AddObjective(
+ new Terms(new Term(new Coefficient(2), variableX), new Term(new Coefficient(1), variableY)), true)
+ .AddConstraints(new Constraints(new Constraint(
+ new Terms(new Term(new Coefficient(1), variableX), new Term(new Coefficient(-1), variableY)),
+ new Interval(0, double.PositiveInfinity))));
+
+ var result = solver.Solve();
+```
+
+### Example 3 (Build and solve ILP)
+
+- Infeasible model: max 2x, s.t. x = 3, variable x binary
+
+```
+var solver = new IntegerLinearProgramSolver()
+ .SetSolverConfigurations(TimeLimitInMilliseconds.Unbounded, 2, true)
+ .AddIntegerVariable(Interval.BinaryInterval, "TestVariable", out var testVariable)
+ .AddObjective(new Terms(new Term(new Coefficient(2), testVariable)), false)
+ .AddConstraints(
+ new Constraints(new Constraint(new Terms(new Term(new Coefficient(1), testVariable)), new Point(3))));
+
+ var result = solver.Solve();
+
+ Logger.Information(result.SolvedSolver.ExportModelAsLpFormat(false));
+```
+
+
+## Contributing
+
+Contributions are welcomed! Read the [Contributing Guide](CONTRIBUTING.md) for more information.
+
+## Licensing
+
+This project is licensed under MIT License. See [LICENSE](LICENSE) for more information.
+
+
+
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..5949fc2
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,7 @@
+# Reporting Security Issues
+
+Please report any security issues you discovered to opensource[at]anexia-it[dot]com
+
+We will assess the risk, plus make a fix available before we create a GitHub issue.
+
+Thank you for your contribution.
diff --git a/dotnetcore-mathematical-program.sln b/dotnetcore-mathematical-program.sln
new file mode 100644
index 0000000..920c898
--- /dev/null
+++ b/dotnetcore-mathematical-program.sln
@@ -0,0 +1,27 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Anexia.MathematicalProgram", "src\Anexia.MathematicalProgram\Anexia.MathematicalProgram.csproj", "{B573BB67-7970-409D-BC59-309D6CC51853}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Anexia.MathematicalProgram.Tests", "test\Anexia.MathematicalProgram.Tests\Anexia.MathematicalProgram.Tests.csproj", "{EB1C030D-619B-41A2-801F-5F34C8465526}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{F531319E-2E84-40DB-95FD-F767AC5AF859}"
+ ProjectSection(SolutionItems) = preProject
+ Directory.Build.props = Directory.Build.props
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B573BB67-7970-409D-BC59-309D6CC51853}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B573BB67-7970-409D-BC59-309D6CC51853}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B573BB67-7970-409D-BC59-309D6CC51853}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B573BB67-7970-409D-BC59-309D6CC51853}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EB1C030D-619B-41A2-801F-5F34C8465526}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EB1C030D-619B-41A2-801F-5F34C8465526}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EB1C030D-619B-41A2-801F-5F34C8465526}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EB1C030D-619B-41A2-801F-5F34C8465526}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/src/Anexia.MathematicalProgram/Anexia.MathematicalProgram.csproj b/src/Anexia.MathematicalProgram/Anexia.MathematicalProgram.csproj
new file mode 100644
index 0000000..6d08c5c
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Anexia.MathematicalProgram.csproj
@@ -0,0 +1,19 @@
+
+
+ net8.0
+ Anexia.MathematicalProgram
+ Anexia.MathematicalProgram
+ enable
+ enable
+ true
+
+
+
+ Anexia.MathematicalProgram
+ anexia;MLackenbucher
+ ANEXIA Internetdienstleistungs GmbH
+ MIT
+ https://github.com/anexia/dotnetcore-mathematical-program/
+ README.md
+
+
diff --git a/src/Anexia.MathematicalProgram/Extensions/EnumExtension.cs b/src/Anexia.MathematicalProgram/Extensions/EnumExtension.cs
new file mode 100644
index 0000000..0cd0d06
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Extensions/EnumExtension.cs
@@ -0,0 +1,33 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using System.Runtime.Serialization;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Extensions;
+
+internal static class EnumExtension
+{
+ internal static string ToEnumString(this T type) where T : Enum
+ {
+ var enumType = typeof(T);
+ var name = Enum.GetName(enumType, type);
+
+ if (name is null) return string.Empty;
+
+ var fieldInfo = enumType.GetField(name);
+
+ if (fieldInfo is null) return string.Empty;
+
+ var enumMemberAttribute =
+ ((EnumMemberAttribute[])fieldInfo.GetCustomAttributes(typeof(EnumMemberAttribute), true)).FirstOrDefault();
+
+ return enumMemberAttribute?.Value ?? string.Empty;
+ }
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/Coefficient.cs b/src/Anexia.MathematicalProgram/Model/Coefficient.cs
new file mode 100644
index 0000000..8a9b0af
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/Coefficient.cs
@@ -0,0 +1,19 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents a coefficient.
+///
+/// The coefficient's value.
+public sealed class Coefficient(double value) : MemberwiseEquatable
+{
+ public double Value { get; } = value;
+
+ ///
+ public override string ToString() => $"{Value:F1}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/Constant.cs b/src/Anexia.MathematicalProgram/Model/Constant.cs
new file mode 100644
index 0000000..6242403
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/Constant.cs
@@ -0,0 +1,21 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents a constant.
+///
+/// The constant's value.
+public sealed class Constant(double value) : MemberwiseEquatable
+{
+ public static readonly Constant Zero = new(0);
+ public double Value { get; } = value;
+ public static Constant operator +(Constant left, double right) => new(left.Value + right);
+ ///
+ public override string ToString() => $"{Value:F1}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/Constraint.cs b/src/Anexia.MathematicalProgram/Model/Constraint.cs
new file mode 100644
index 0000000..585a7a2
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/Constraint.cs
@@ -0,0 +1,21 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents a constraint.
+///
+/// A list of terms.
+/// The lower and upper bound for the sum of the terms.
+public sealed class Constraint(Terms terms, IInterval interval) : MemberwiseEquatable
+{
+ public Terms Terms { get; } = terms;
+ public IInterval Interval { get; } = interval;
+
+ ///
+ public override string ToString() => $"{nameof(Interval)}: {Interval}, {nameof(Terms)}: {Terms}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/Constraints.cs b/src/Anexia.MathematicalProgram/Model/Constraints.cs
new file mode 100644
index 0000000..05aa602
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/Constraints.cs
@@ -0,0 +1,69 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using System.Collections;
+using System.Collections.Immutable;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents an immutable list of constraints.
+///
+/// The constraints.
+public sealed class Constraints(ImmutableList elements) : IEnumerable, IEquatable
+{
+ private ImmutableList Elements { get; } = elements;
+
+ public Constraints(IEnumerable elements)
+ : this(elements.ToImmutableList())
+ {
+ }
+
+ public Constraints(params Constraint[] values)
+ : this(values.ToImmutableList())
+ {
+ }
+
+ public Constraints()
+ : this(ImmutableList.Empty)
+ {
+ }
+
+
+ ///
+ public bool Equals(Constraints? other)
+ {
+ if (other is null) return false;
+
+ return ReferenceEquals(this, other) || Elements.SequenceEqual(other.Elements);
+ }
+
+ ///
+ public override bool Equals(object? obj) => ReferenceEquals(this, obj) || obj is Constraints other && Equals(other);
+
+ ///
+ public override int GetHashCode() => Elements.GetHashCode();
+
+ ///
+ /// Adds the given constraint.
+ ///
+ /// The constraint to be added.
+ /// A new object with the constraint added.
+ public Constraints Add(Constraint constraint) => new(Elements.Add(constraint));
+
+ ///
+ public IEnumerator GetEnumerator() => Elements.GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ public override string ToString() => string.Join(",", Elements);
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/IInterval.cs b/src/Anexia.MathematicalProgram/Model/IInterval.cs
new file mode 100644
index 0000000..1047471
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/IInterval.cs
@@ -0,0 +1,23 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents an Interval.
+///
+public interface IInterval
+{
+ ///
+ /// The interval's lower bound.
+ ///
+ public LowerBound LowerBound { get; }
+
+ ///
+ /// The interval's upper bound.
+ ///
+ public UpperBound UpperBound { get; }
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/InadmissibleBoundsException.cs b/src/Anexia.MathematicalProgram/Model/InadmissibleBoundsException.cs
new file mode 100644
index 0000000..c2d8165
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/InadmissibleBoundsException.cs
@@ -0,0 +1,23 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// An exception where the lower bound is greater than the upper bound.
+///
+public sealed class InadmissibleBoundsException : Exception
+{
+ ///
+ /// Initializes a new instance of with given bounds.
+ ///
+ /// The lower bound.
+ /// The upper bound.
+ internal InadmissibleBoundsException(LowerBound lowerBound, UpperBound upperBound)
+ : base($"Lower bound {lowerBound} is larger than upper bound {upperBound}")
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/Interval.cs b/src/Anexia.MathematicalProgram/Model/Interval.cs
new file mode 100644
index 0000000..74b3046
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/Interval.cs
@@ -0,0 +1,49 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents a simple bounded interval with inclusive upper and lower bounds.
+///
+public sealed class Interval : MemberwiseEquatable, IInterval
+{
+ ///
+ /// An interval with bounds [0,1].
+ ///
+ public static readonly Interval BinaryInterval = new(0, 1);
+
+ ///
+ /// Initializes an instance of type with the given bounds.
+ ///
+ /// The lower bound.
+ /// The upper bound.
+ /// Throws an when the lower bound is grater than the upper bound.
+ public Interval(LowerBound lowerBound, UpperBound upperBound)
+ {
+ if (lowerBound > upperBound) throw new InadmissibleBoundsException(lowerBound, upperBound);
+ LowerBound = lowerBound;
+ UpperBound = upperBound;
+ }
+
+ internal Interval(double lowerBound, double upperBound)
+ : this(new LowerBound(lowerBound), new UpperBound(upperBound))
+ {
+ }
+
+ ///
+ /// The lower bound.
+ ///
+ public LowerBound LowerBound { get; }
+
+ ///
+ /// The upper bound.
+ ///
+ public UpperBound UpperBound { get; }
+
+ ///
+ public override string ToString() => $"Interval [{LowerBound},{UpperBound}]";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/LowerBound.cs b/src/Anexia.MathematicalProgram/Model/LowerBound.cs
new file mode 100644
index 0000000..acfa75e
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/LowerBound.cs
@@ -0,0 +1,53 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents a lower bound.
+///
+/// The lower bound's value.
+public sealed class LowerBound(double value) : MemberwiseEquatable
+{
+ public double Value { get; } = value;
+
+ ///
+ /// Checks if a given lower bound is less than or equal to an upper bound.
+ ///
+ /// The lower bound.
+ /// The upper bound.
+ /// True, when the given lower bound is less than or equal to the given upper bound. False, otherwise.
+ public static bool operator <=(LowerBound lowerBound, UpperBound upperBound) =>
+ lowerBound.Value <= upperBound.Value;
+
+ ///
+ /// Checks if a given lower bound is greater than or equal to an upper bound.
+ ///
+ /// The lower bound.
+ /// The upper bound.
+ /// True, when the given lower bound is grater than or equal to the given upper bound. False, otherwise.
+ public static bool operator >=(LowerBound lowerBound, UpperBound upperBound) =>
+ lowerBound.Value >= upperBound.Value;
+
+ ///
+ /// Checks if a given lower bound is less than an upper bound.
+ ///
+ /// The lower bound.
+ /// The upper bound.
+ /// True, when the given lower bound is less than to the given upper bound. False, otherwise.
+ public static bool operator <(LowerBound lowerBound, UpperBound upperBound) => lowerBound.Value < upperBound.Value;
+
+ ///
+ /// Checks if a given lower bound is greater than an upper bound.
+ ///
+ /// The lower bound.
+ /// The upper bound.
+ /// True, when the given lower bound is grater than the given upper bound. False, otherwise.
+ public static bool operator >(LowerBound lowerBound, UpperBound upperBound) => lowerBound.Value > upperBound.Value;
+
+ ///
+ public override string ToString() => $"{Value:F1}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/Point.cs b/src/Anexia.MathematicalProgram/Model/Point.cs
new file mode 100644
index 0000000..5312342
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/Point.cs
@@ -0,0 +1,42 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents an inclusive interval where the lower bound equals the upper bound.
+///
+public sealed class Point : MemberwiseEquatable, IInterval
+{
+ ///
+ /// Represents an interval [1,1]
+ ///
+ public static readonly Point One = new(1);
+
+ public Point(double value)
+ : this(new LowerBound(value), new UpperBound(value))
+ {
+ }
+
+ private Point(LowerBound lowerBound, UpperBound upperBound)
+ {
+ LowerBound = lowerBound;
+ UpperBound = upperBound;
+ }
+
+ ///
+ /// The lower bound.
+ ///
+ public LowerBound LowerBound { get; }
+
+ ///
+ /// The upper bound.
+ ///
+ public UpperBound UpperBound { get; }
+
+ ///
+ public override string ToString() => $"Point [{LowerBound},{UpperBound}]";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/Term.cs b/src/Anexia.MathematicalProgram/Model/Term.cs
new file mode 100644
index 0000000..f1d9dd5
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/Term.cs
@@ -0,0 +1,36 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Google.OrTools.LinearSolver;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents a term consisting of a coefficient and a variable. The value of the Term is given by
+/// * .
+///
+/// The coefficient.
+/// The variable.
+public sealed class Term(Coefficient coefficient, Variable variable) : MemberwiseEquatable
+{
+ ///
+ /// The coefficient.
+ ///
+ public Coefficient Coefficient { get; } = coefficient;
+
+ ///
+ /// The variable.
+ ///
+ public Variable Variable { get; } = variable;
+
+ ///
+ public override string ToString() =>
+ $"{nameof(Coefficient)} * ({nameof(Variable)} Name, {nameof(Variable)} HashCode): {Coefficient} * ({Variable.Name()}, {Variable.GetHashCode()})";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/Terms.cs b/src/Anexia.MathematicalProgram/Model/Terms.cs
new file mode 100644
index 0000000..e20c6b6
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/Terms.cs
@@ -0,0 +1,79 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using System.Collections;
+using System.Collections.Immutable;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents a set of terms.
+///
+/// A set of terms.
+public sealed class Terms(ImmutableHashSet elements) : IEquatable, IEnumerable
+{
+ ///
+ /// Initializes a new instance of with given terms.
+ ///
+ /// An enumerable of terms.
+ public Terms(IEnumerable elements)
+ : this(elements.ToImmutableHashSet())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of with given terms.
+ ///
+ /// A params list of terms.
+ public Terms(params Term[] values)
+ : this(values.ToImmutableList())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of with empty terms.
+ ///
+ public Terms()
+ : this(ImmutableHashSet.Empty)
+ {
+ }
+
+ ///
+ public bool Equals(Terms? other)
+ {
+ if (other is null) return false;
+
+ return ReferenceEquals(this, other) || Elements.SequenceEqual(other.Elements);
+ }
+
+ ///
+ public override bool Equals(object? obj) => ReferenceEquals(this, obj) || obj is Terms other && Equals(other);
+
+ ///
+ public override int GetHashCode() => Elements.GetHashCode();
+
+ private ImmutableHashSet Elements { get; } = elements;
+
+ ///
+ /// Adds the given term to the end of the immutable list.
+ ///
+ /// The term to be added.
+ /// A new object with the term added.
+ public Terms Add(Term term) => new(Elements.Add(term));
+
+ ///
+ public IEnumerator GetEnumerator() => Elements.GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ public override string ToString() => string.Join(",", Elements);
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/UpperBound.cs b/src/Anexia.MathematicalProgram/Model/UpperBound.cs
new file mode 100644
index 0000000..d2d74ea
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/UpperBound.cs
@@ -0,0 +1,53 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents an upper bound.
+///
+/// The upper bound's value.
+public sealed class UpperBound(double value) : MemberwiseEquatable
+{
+ public double Value { get; } = value;
+
+ ///
+ /// Checks if a given upper bound is less than or equal to a lower bound.
+ ///
+ /// The upper bound.
+ /// The lower bound.
+ /// True, when the given upper bound is less than or equal to the given lower bound. False, otherwise.
+ public static bool operator <=(UpperBound upperBound, LowerBound lowerBound) =>
+ upperBound.Value <= lowerBound.Value;
+
+ ///
+ /// Checks if a given upper bound is greater than or equal to an lower bound.
+ ///
+ /// The upper bound.
+ /// The lower bound.
+ /// True, when the given upper bound is grater than or equal to the given lower bound. False, otherwise.
+ public static bool operator >=(UpperBound upperBound, LowerBound lowerBound) =>
+ upperBound.Value >= lowerBound.Value;
+
+ ///
+ /// Checks if a given upper bound is less than an lower bound.
+ ///
+ /// The upper bound.
+ /// The lower bound.
+ /// True, when the given upper bound is less than to the given lower bound. False, otherwise.
+ public static bool operator <(UpperBound upperBound, LowerBound lowerBound) => upperBound.Value < lowerBound.Value;
+
+ ///
+ /// Checks if a given upper bound is greater than an lower bound.
+ ///
+ /// The upper bound.
+ /// The lower bound.
+ /// True, when the given upper bound is grater than the given lower bound. False, otherwise.
+ public static bool operator >(UpperBound upperBound, LowerBound lowerBound) => upperBound.Value > lowerBound.Value;
+
+ ///
+ public override string ToString() => $"{Value:F1}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Model/WarningMessage.cs b/src/Anexia.MathematicalProgram/Model/WarningMessage.cs
new file mode 100644
index 0000000..7d57b63
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Model/WarningMessage.cs
@@ -0,0 +1,19 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Model;
+
+///
+/// Represents a warning message.
+///
+/// A message.
+public sealed class WarningMessage(string message) : MemberwiseEquatable
+{
+ public string Message { get; } = message;
+
+ ///
+ public override string ToString() => $"{nameof(Message)}: {Message}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Result/IsFeasible.cs b/src/Anexia.MathematicalProgram/Result/IsFeasible.cs
new file mode 100644
index 0000000..f460717
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Result/IsFeasible.cs
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Result;
+
+///
+/// Represents an object containing feasibility information.
+///
+/// Boolean representing feasible when true, or unfeasible when false.
+public sealed class IsFeasible(bool value) : MemberwiseEquatable
+{
+ ///
+ /// True for feasible, false when not.
+ ///
+ public bool Value { get; } = value;
+
+ ///
+ public override string ToString() => $"{Value}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Result/IsOptimal.cs b/src/Anexia.MathematicalProgram/Result/IsOptimal.cs
new file mode 100644
index 0000000..1fcf5bf
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Result/IsOptimal.cs
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Result;
+
+///
+/// Represents an object containing optimality information.
+///
+/// Boolean representing optimal when true, or not optimal when false.
+public sealed class IsOptimal(bool value) : MemberwiseEquatable
+{
+ ///
+ /// True for optimal, false when not.
+ ///
+ public bool Value { get; } = value;
+
+ ///
+ public override string ToString() => $"{Value}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Result/ObjectiveValue.cs b/src/Anexia.MathematicalProgram/Result/ObjectiveValue.cs
new file mode 100644
index 0000000..6ff7628
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Result/ObjectiveValue.cs
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Result;
+
+///
+/// Represents an objective value.
+///
+/// The objective's value.
+public sealed class ObjectiveValue(double value) : MemberwiseEquatable
+{
+ ///
+ /// The objective value.
+ ///
+ public double Value { get; } = value;
+
+ ///
+ public override string ToString() => $"{Value}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Result/OptimalityGap.cs b/src/Anexia.MathematicalProgram/Result/OptimalityGap.cs
new file mode 100644
index 0000000..a11f7f4
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Result/OptimalityGap.cs
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Result;
+
+///
+/// Represents an optimality gap.
+///
+/// The gap's value.
+public sealed class OptimalityGap(double value) : MemberwiseEquatable
+{
+ ///
+ /// The optimality gap.
+ ///
+ public double Value { get; } = value;
+
+ ///
+ public override string ToString() => $"{Value}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Result/ResultHandling.cs b/src/Anexia.MathematicalProgram/Result/ResultHandling.cs
new file mode 100644
index 0000000..bb5efad
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Result/ResultHandling.cs
@@ -0,0 +1,60 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Anexia.MathematicalProgram.Solve;
+using Google.OrTools.LinearSolver;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Result;
+
+internal sealed class ResultHandling(Solver solver) : MemberwiseEquatable
+{
+ private Solver Solver { get; } = solver;
+
+ internal SolverResult Handle(Solver.ResultStatus resultStatus)
+ {
+ switch (resultStatus)
+ {
+ case Solver.ResultStatus.OPTIMAL:
+ return new SolverResult(Solver,
+ new ObjectiveValue(Solver.Objective().Value()),
+ new IsFeasible(true),
+ new IsOptimal(true),
+ new OptimalityGap(0));
+ case Solver.ResultStatus.FEASIBLE:
+ var objective = Solver.Objective();
+ var objectiveValue = objective.Value();
+
+ return new SolverResult(Solver,
+ new ObjectiveValue(objectiveValue),
+ new IsFeasible(true),
+ new IsOptimal(false),
+ new OptimalityGap(Math.Abs(objective.BestBound() - objectiveValue) / objectiveValue));
+ case Solver.ResultStatus.INFEASIBLE:
+ return new SolverResult(Solver,
+ new ObjectiveValue(double.NaN),
+ new IsFeasible(false),
+ new IsOptimal(false),
+ new OptimalityGap(double.NaN));
+ case Solver.ResultStatus.UNBOUNDED:
+ throw new MathematicalProgramException("Mathematical program is unbounded.");
+ case Solver.ResultStatus.ABNORMAL:
+ throw new MathematicalProgramException("Mathematical program is abnormal (probably numerical error).");
+ case Solver.ResultStatus.NOT_SOLVED:
+ throw new MathematicalProgramException("Mathematical program could not be diagnosed and solved.");
+ case Solver.ResultStatus.MODEL_INVALID:
+ throw new MathematicalProgramException("Mathematical model is not valid.");
+ default:
+ throw new MathematicalProgramException("Unknown result status in linear solver.");
+ }
+ }
+
+ ///
+ public override string ToString() => $"{nameof(Solver)}: {Solver}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Result/SolverResult.cs b/src/Anexia.MathematicalProgram/Result/SolverResult.cs
new file mode 100644
index 0000000..2c813b4
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Result/SolverResult.cs
@@ -0,0 +1,58 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Google.OrTools.LinearSolver;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Result;
+
+///
+/// Represents the solver's result.
+///
+/// The original solver used.
+/// The objective value.
+/// Information, whether the model was feasible, or not.
+/// Information, whether the solution is optimal, or not.
+/// The optimality gap.
+public sealed class SolverResult(
+ Solver solvedSolver,
+ ObjectiveValue objectiveValue,
+ IsFeasible isFeasible,
+ IsOptimal isOptimal,
+ OptimalityGap optimalityGap) : MemberwiseEquatable
+{
+ ///
+ /// The original solver used.
+ ///
+ public Solver SolvedSolver { get; } = solvedSolver;
+
+ ///
+ /// The objective value.
+ ///
+ public ObjectiveValue ObjectiveValue { get; } = objectiveValue;
+
+ ///
+ /// Information, whether the model was feasible, or not.
+ ///
+ public IsFeasible IsFeasible { get; } = isFeasible;
+
+ ///
+ /// Information, whether the solution is optimal, or not.
+ ///
+ public IsOptimal IsOptimal { get; } = isOptimal;
+
+ ///
+ /// The optimality gap.
+ ///
+ public OptimalityGap OptimalityGap { get; } = optimalityGap;
+
+ ///
+ public override string ToString() =>
+ $"{nameof(ObjectiveValue)}: {ObjectiveValue}, {nameof(IsFeasible)}: {IsFeasible}, {nameof(IsOptimal)}: {IsOptimal}, {nameof(OptimalityGap)}: {OptimalityGap}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Solve/IntegerLinearProgramSolver.cs b/src/Anexia.MathematicalProgram/Solve/IntegerLinearProgramSolver.cs
new file mode 100644
index 0000000..04f3919
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Solve/IntegerLinearProgramSolver.cs
@@ -0,0 +1,211 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Anexia.MathematicalProgram.Extensions;
+using Anexia.MathematicalProgram.Model;
+using Anexia.MathematicalProgram.Result;
+using Anexia.MathematicalProgram.SolverConfiguration;
+using Google.OrTools.LinearSolver;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Solve;
+
+///
+/// Represents a solver for solving Integer Linear Programming models.
+///
+public sealed class IntegerLinearProgramSolver : MemberwiseEquatable, IDisposable
+{
+ private IntegerLinearProgramSolver(Solver solver, IlpSolverType solverType)
+ {
+ Solver = solver;
+ SolverType = solverType;
+ }
+
+ ///
+ /// Initializes a new instance of with an optional
+ /// . When the is not specified, the default solver is
+ /// CBC.
+ /// If the given solver type is either not supported, or no licence is available, it is switched to
+ /// CBC.
+ ///
+ /// The desired solver type.
+ public IntegerLinearProgramSolver(IlpSolverType solverType = IlpSolverType.CbcMixedIntegerProgramming)
+ : this(Solver.CreateSolver(CheckTypeSupportedOrSwitchToCbc(solverType).ToEnumString()),
+ CheckTypeSupportedOrSwitchToCbc(solverType))
+ {
+ }
+
+ ///
+ /// Creates an instance of with a given solver type.
+ /// If the given solver type is either not supported, or no licence is available, it is switched to
+ /// CBC. Furthermore, a warning message is set via
+ /// .
+ ///
+ /// The desired solver type.
+ /// A warning message when the solver tye gets switched automatically.
+ /// A new instance of .
+ public static IntegerLinearProgramSolver Create(IlpSolverType solverType, out WarningMessage? message)
+ {
+ message = null;
+
+ if (Solver.SupportsProblemType(Enum.Parse(solverType.ToEnumString())))
+ return new IntegerLinearProgramSolver(solverType);
+
+ message = new(
+ $"Solver type {solverType} is not supported -> Switched to CBC. There might be no valid licence or an unsupported Gurobi version.");
+
+ return new IntegerLinearProgramSolver();
+ }
+
+ private static IlpSolverType CheckTypeSupportedOrSwitchToCbc(IlpSolverType solverType) =>
+ Solver.SupportsProblemType(Enum.Parse(solverType.ToEnumString()))
+ ? solverType
+ : IlpSolverType.CbcMixedIntegerProgramming;
+
+ private Solver Solver { get; }
+ private IlpSolverType SolverType { get; }
+
+ ///
+ /// Returns the model in LP format.
+ ///
+ /// Specifies whether the model should be obfuscated, or not. True by default.
+ /// The model.
+ public string ModelAsLpFormat(bool obfuscated = true) => Solver.ExportModelAsLpFormat(obfuscated);
+
+ ///
+ /// Returns the number of constraints.
+ ///
+ /// The number of constraints.
+ public int NumberOfConstraints() => Solver.NumConstraints();
+
+ ///
+ /// Returns the number of variables.
+ ///
+ /// The number of variables.
+ public int NumberOfVariables() => Solver.NumVariables();
+
+ ///
+ /// Adds a new integer variable to the solver and returns it as an out parameter.
+ ///
+ /// The desired variable's interval.
+ /// The desired variable's name.
+ /// The newly created variable.
+ /// The updated solver.
+ public IntegerLinearProgramSolver AddIntegerVariable(IInterval interval, string variableName, out Variable variable)
+ {
+ variable = Solver.MakeIntVar(interval.LowerBound.Value, interval.UpperBound.Value, variableName);
+
+ return this;
+ }
+
+ ///
+ /// Adds the given constraints to the solver.
+ ///
+ /// The constraints to be added.
+ /// The updated solver.
+ public IntegerLinearProgramSolver AddConstraints(Constraints constraints)
+ {
+ foreach (var constraint in constraints)
+ {
+ var interval = constraint.Interval;
+ var solverConstraint = Solver.MakeConstraint(interval.LowerBound.Value, interval.UpperBound.Value);
+
+ foreach (var term in constraint.Terms)
+ solverConstraint.SetCoefficient(term.Variable, term.Coefficient.Value);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Sets the objective function of the solver.
+ ///
+ /// The terms of the objective function.
+ /// Boolean whether to minimize or maximize.
+ /// The updated solver.
+ public IntegerLinearProgramSolver SetObjective(Terms terms, bool minimize) =>
+ SetObjective(terms, Constant.Zero, minimize);
+
+ ///
+ /// Sets the objective function of the solver.
+ ///
+ /// The terms of the objective function.
+ /// An additional constant offset.
+ /// Boolean whether to minimize or maximize.
+ /// The updated solver.
+ public IntegerLinearProgramSolver SetObjective(Terms terms, Constant constant, bool minimize)
+ {
+ var objective = Solver.Objective();
+ objective.Clear();
+
+ foreach (var term in terms) objective.SetCoefficient(term.Variable, term.Coefficient.Value);
+
+ objective.SetOffset(constant.Value);
+
+ if (minimize)
+ objective.SetMinimization();
+ else
+ objective.SetMaximization();
+
+ return this;
+ }
+
+ ///
+ /// Starts the solving process.
+ ///
+ /// The result after solving.
+ /// Throws a if an error occured while solving.
+ /// Furthermore, when the result status is anything other than feasible, infeasible or optimal.
+ ///
+ public SolverResult Solve() => Solve(new SolverParameter());
+
+ ///
+ /// Starts the solving process with additional parameters.
+ ///
+ /// The parameters to be used by the solver.
+ /// The result.
+ /// Throws a if an error occured while solving.
+ /// Furthermore, when the result status is anything other than feasible, infeasible or optimal.
+ ///
+ public SolverResult Solve(SolverParameter solverParameter)
+ {
+ try
+ {
+ _ = Solver.SetNumThreads((int)(solverParameter.NumberOfThreads?.Value ?? 0));
+
+ if (solverParameter.TimeLimitInMilliseconds is not null)
+ Solver.SetTimeLimit(solverParameter.TimeLimitInMilliseconds.Value);
+
+ if (solverParameter.EnableSolverOutput.Value) Solver.EnableOutput();
+ using var parameter = new MPSolverParameters();
+
+ parameter.SetDoubleParam(MPSolverParameters.DoubleParam.RELATIVE_MIP_GAP,
+ solverParameter.RelativeGap.Value);
+
+ var resultStatus = Solver.Solve(parameter);
+
+ return new ResultHandling(Solver).Handle(resultStatus);
+ }
+ catch (Exception exception)
+ {
+ throw new MathematicalProgramException(exception);
+ }
+ }
+
+
+ ///
+ public void Dispose()
+ {
+ Solver.Clear();
+ Solver.Dispose();
+ }
+
+ ///
+ public override string ToString() => $"IntegerLinearProgrammingSolver {SolverType.ToEnumString()}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Solve/LinearProgramSolver.cs b/src/Anexia.MathematicalProgram/Solve/LinearProgramSolver.cs
new file mode 100644
index 0000000..32d539d
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Solve/LinearProgramSolver.cs
@@ -0,0 +1,158 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Anexia.MathematicalProgram.Model;
+using Anexia.MathematicalProgram.Result;
+using Anexia.MathematicalProgram.SolverConfiguration;
+using Google.OrTools.LinearSolver;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Solve;
+
+///
+/// Represents a solver for solving Linear Programming models.
+///
+public sealed class LinearProgramSolver : MemberwiseEquatable, IDisposable
+{
+ private LinearProgramSolver(Solver solver)
+ {
+ Solver = solver;
+ }
+
+ ///
+ /// Initializes a new instance of using
+ /// GLOP.
+ ///
+ public LinearProgramSolver()
+ : this(Solver.CreateSolver(LpSolverType.Glop.ToString()))
+ {
+ }
+
+ private Solver Solver { get; }
+
+ ///
+ /// Adds a new continuous variable to the solver and returns it as an out parameter.
+ ///
+ /// The desired variable's interval.
+ /// The desired variable's name.
+ /// The newly created variable.
+ /// The updated solver.
+ public LinearProgramSolver AddContinuousVariable(IInterval interval, string variableName, out Variable variable)
+ {
+ variable = Solver.MakeNumVar(interval.LowerBound.Value, interval.UpperBound.Value, variableName);
+
+ return this;
+ }
+
+ ///
+ /// Adds the given constraints to the solver.
+ ///
+ /// The constraints to be added.
+ /// The updated solver.
+ public LinearProgramSolver AddConstraints(Constraints constraints)
+ {
+ foreach (var constraint in constraints)
+ {
+ var interval = constraint.Interval;
+ var solverConstraint = Solver.MakeConstraint(interval.LowerBound.Value, interval.UpperBound.Value);
+
+ foreach (var term in constraint.Terms)
+ solverConstraint.SetCoefficient(term.Variable, term.Coefficient.Value);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Sets the objective function of the solver.
+ ///
+ /// The terms of the objective function.
+ /// Boolean whether to minimize or maximize.
+ /// The updated solver.
+ public LinearProgramSolver SetObjective(Terms terms, bool minimize) => SetObjective(terms, Constant.Zero, minimize);
+
+ ///
+ /// Sets the objective function of the solver.
+ ///
+ /// The terms of the objective function.
+ /// An additional constant offset.
+ /// Boolean whether to minimize or maximize.
+ /// The updated solver.
+ public LinearProgramSolver SetObjective(Terms terms, Constant constant, bool minimize)
+ {
+ var objective = Solver.Objective();
+ objective.Clear();
+
+ foreach (var term in terms) objective.SetCoefficient(term.Variable, term.Coefficient.Value);
+
+ objective.SetOffset(constant.Value);
+
+ if (minimize)
+ objective.SetMinimization();
+ else
+ objective.SetMaximization();
+
+ return this;
+ }
+
+ ///
+ /// Starts the solving process.
+ ///
+ /// The result after solving.
+ /// Throws a if an error occured while solving.
+ /// Furthermore, when the result status is anything other than feasible, infeasible or optimal.
+ ///
+ public SolverResult Solve() => Solve(new SolverParameter());
+
+ ///
+ /// Starts the solving process with additional parameters.
+ ///
+ /// The parameters to be used by the solver.
+ /// The result after solving.
+ /// Throws a if an error occured while solving.
+ /// Furthermore, when the result status is anything other than feasible, infeasible or optimal.
+ ///
+ public SolverResult Solve(SolverParameter solverParameter)
+ {
+ try
+ {
+ _ = Solver.SetNumThreads((int)(solverParameter.NumberOfThreads?.Value ?? 0));
+
+ if (solverParameter.TimeLimitInMilliseconds is not null)
+ Solver.SetTimeLimit(solverParameter.TimeLimitInMilliseconds.Value);
+
+ if (solverParameter.EnableSolverOutput.Value) Solver.EnableOutput();
+
+ var parameter = new MPSolverParameters();
+
+ parameter.SetDoubleParam(MPSolverParameters.DoubleParam.RELATIVE_MIP_GAP,
+ solverParameter.RelativeGap.Value);
+
+ var resultStatus = Solver.Solve(parameter);
+
+ return new ResultHandling(Solver).Handle(resultStatus);
+ }
+ catch (Exception exception)
+ {
+ Console.WriteLine(exception);
+
+ throw new MathematicalProgramException(exception);
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ Solver.Clear();
+ Solver.Dispose();
+ }
+
+ ///
+ public override string ToString() => "Google.OrTools.LinearSolver Glop";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/Solve/MathematicalProgramException.cs b/src/Anexia.MathematicalProgram/Solve/MathematicalProgramException.cs
new file mode 100644
index 0000000..d04e06c
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/Solve/MathematicalProgramException.cs
@@ -0,0 +1,23 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.Solve;
+
+///
+/// An exception occured while solving a model.
+///
+public sealed class MathematicalProgramException : Exception
+{
+ internal MathematicalProgramException(Exception exception)
+ : base($"Error in solver: {exception.Message}, {exception.InnerException}", exception)
+ {
+ }
+
+ internal MathematicalProgramException(string message)
+ : base(message)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/SolverConfiguration/EnableSolverOutput.cs b/src/Anexia.MathematicalProgram/SolverConfiguration/EnableSolverOutput.cs
new file mode 100644
index 0000000..1a7f528
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/SolverConfiguration/EnableSolverOutput.cs
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.SolverConfiguration;
+
+///
+/// Represents a setting whether to enable solver log output on the console, or not.
+///
+/// True to enable solver console log output, false to disable.
+public sealed class EnableSolverOutput(bool value) : MemberwiseEquatable
+{
+ public static readonly EnableSolverOutput True = new(true);
+ public static readonly EnableSolverOutput False = new(false);
+
+ public bool Value { get; } = value;
+
+ ///
+ public override string ToString() => $"{nameof(Value)}: {Value}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/SolverConfiguration/ILPSolverType.cs b/src/Anexia.MathematicalProgram/SolverConfiguration/ILPSolverType.cs
new file mode 100644
index 0000000..c9ec0b1
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/SolverConfiguration/ILPSolverType.cs
@@ -0,0 +1,31 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using System.Runtime.Serialization;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.SolverConfiguration;
+
+///
+/// The supported ILP solver types.
+///
+public enum IlpSolverType
+{
+ ///
+ /// CBC solver.
+ ///
+ [EnumMember(Value = "CBC_MIXED_INTEGER_PROGRAMMING")]
+ CbcMixedIntegerProgramming,
+
+ ///
+ /// Gurobi solver. A licence is needed for usage.
+ ///
+ [EnumMember(Value = "GUROBI_MIXED_INTEGER_PROGRAMMING")]
+ GurobiMixedIntegerProgramming
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/SolverConfiguration/LPSolverType.cs b/src/Anexia.MathematicalProgram/SolverConfiguration/LPSolverType.cs
new file mode 100644
index 0000000..387c052
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/SolverConfiguration/LPSolverType.cs
@@ -0,0 +1,18 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.SolverConfiguration;
+
+///
+/// The supported LP solvers.
+///
+public enum LpSolverType
+{
+ ///
+ /// GLOP solver.
+ ///
+ Glop
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/SolverConfiguration/NumberOfThreads.cs b/src/Anexia.MathematicalProgram/SolverConfiguration/NumberOfThreads.cs
new file mode 100644
index 0000000..a0c100f
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/SolverConfiguration/NumberOfThreads.cs
@@ -0,0 +1,19 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.SolverConfiguration;
+
+///
+/// Represents the number of threads to be used by the solver.
+///
+/// The value.
+public sealed class NumberOfThreads(uint value) : MemberwiseEquatable
+{
+ public uint Value { get; } = value;
+
+ ///
+ public override string ToString() => $"{nameof(Value)}: {Value}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/SolverConfiguration/RelativeGap.cs b/src/Anexia.MathematicalProgram/SolverConfiguration/RelativeGap.cs
new file mode 100644
index 0000000..b00927f
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/SolverConfiguration/RelativeGap.cs
@@ -0,0 +1,28 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.SolverConfiguration;
+
+///
+/// Represents the relative gap when the solver terminates. It is an upper bound on the actual MIP gap given by (|ObjBound - ObjValue|) / |ObjValue|.
+///
+/// The gap.
+public sealed class RelativeGap(double relativeGap) : MemberwiseEquatable
+{
+ public static readonly RelativeGap EMinus7 = new(0.0000001);
+
+ public double Value { get; } = relativeGap;
+
+ ///
+ /// Calculates the given negative power of 10, i.e., 10^-negativeExponent
+ ///
+ /// The exponent of 10^-1
+ /// The calculated value.
+ public static RelativeGap FromEMinus(uint negativeExponent) => new(Math.Pow(10, -negativeExponent));
+
+ ///
+ public override string ToString() => $"{nameof(Value)}: {Value}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/SolverConfiguration/SolverParameter.cs b/src/Anexia.MathematicalProgram/SolverConfiguration/SolverParameter.cs
new file mode 100644
index 0000000..d700795
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/SolverConfiguration/SolverParameter.cs
@@ -0,0 +1,77 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.SolverConfiguration;
+
+///
+/// Represents the parameters that can be set for a solver.
+///
+/// Whether to enable the solver's underlying log output.
+/// Time limit of the solving process.
+/// The number of threads that should be used by the solver.
+/// The relative gap when the solver should terminate.
+public sealed class SolverParameter(
+ EnableSolverOutput enableSolverOutput,
+ RelativeGap relativeGap,
+ TimeLimitInMilliseconds? timeLimitInMilliseconds = null,
+ NumberOfThreads? numberOfThreads = null) : MemberwiseEquatable
+{
+ ///
+ /// Create default solver parameters with given time limit.
+ ///
+ /// The time limit in milliseconds.
+ ///
+ /// ParameterDescription
+ /// - NumberOfThreadsDefault value: null
+ /// - EnableSolverOutputDefault value: false
+ /// - RelativeGapDefault value: E-7
+ ///
+ public SolverParameter(TimeLimitInMilliseconds timeLimitInMilliseconds)
+ : this(EnableSolverOutput.False, RelativeGap.EMinus7, timeLimitInMilliseconds, new NumberOfThreads(0))
+ {
+ }
+
+ ///
+ /// Create solver parameters without a time limit.
+ ///
+ ///
+ /// ParameterDescription
+ /// - TimeLimitInMillisecondsDefault value: null
+ /// - NumberOfThreadsDefault value: null
+ /// - EnableSolverOutputDefault value: false
+ /// - RelativeGapDefault value: E-7
+ ///
+ public SolverParameter(EnableSolverOutput enableSolverOutput,
+ NumberOfThreads numberOfThreads,
+ RelativeGap relativeGap)
+ : this(enableSolverOutput, relativeGap, null, numberOfThreads)
+ {
+ }
+
+ ///
+ /// Create default solver parameters
+ ///
+ ///
+ /// ParameterDescription
+ /// - TimeLimitInMillisecondsDefault value: null
+ /// - NumberOfThreadsDefault value: null
+ /// - EnableSolverOutputDefault value: false
+ /// - RelativeGapDefault value: E-7
+ ///
+ public SolverParameter()
+ : this(EnableSolverOutput.False, new NumberOfThreads(0), RelativeGap.EMinus7)
+ {
+ }
+
+ public RelativeGap RelativeGap { get; } = relativeGap;
+ public TimeLimitInMilliseconds? TimeLimitInMilliseconds { get; } = timeLimitInMilliseconds;
+ public EnableSolverOutput EnableSolverOutput { get; } = enableSolverOutput;
+ public NumberOfThreads? NumberOfThreads { get; } = numberOfThreads;
+
+ ///
+ public override string ToString() =>
+ $"{nameof(RelativeGap)}: {RelativeGap}, {nameof(TimeLimitInMilliseconds)}: {TimeLimitInMilliseconds}, {nameof(EnableSolverOutput)}: {EnableSolverOutput}, {nameof(NumberOfThreads)}: {NumberOfThreads}";
+}
\ No newline at end of file
diff --git a/src/Anexia.MathematicalProgram/SolverConfiguration/TimeLimitInMilliseconds.cs b/src/Anexia.MathematicalProgram/SolverConfiguration/TimeLimitInMilliseconds.cs
new file mode 100644
index 0000000..13a23cb
--- /dev/null
+++ b/src/Anexia.MathematicalProgram/SolverConfiguration/TimeLimitInMilliseconds.cs
@@ -0,0 +1,19 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+namespace Anexia.MathematicalProgram.SolverConfiguration;
+
+///
+/// The time limit.
+///
+///
+public sealed class TimeLimitInMilliseconds(uint value) : MemberwiseEquatable
+{
+ public uint Value { get; } = value;
+
+ ///
+ public override string ToString() => $"{nameof(Value)}: {Value}";
+}
\ No newline at end of file
diff --git a/test/Anexia.MathematicalProgram.Tests/Anexia.MathematicalProgram.Tests.csproj b/test/Anexia.MathematicalProgram.Tests/Anexia.MathematicalProgram.Tests.csproj
new file mode 100644
index 0000000..da7b273
--- /dev/null
+++ b/test/Anexia.MathematicalProgram.Tests/Anexia.MathematicalProgram.Tests.csproj
@@ -0,0 +1,17 @@
+
+
+
+ enable
+
+ false
+
+ Anexia.MathematicalProgram.Tests
+
+ Anexia.MathematicalProgram.Tests
+
+
+
+
+
+
+
diff --git a/test/Anexia.MathematicalProgram.Tests/Factory/ConstraintFactory.cs b/test/Anexia.MathematicalProgram.Tests/Factory/ConstraintFactory.cs
new file mode 100644
index 0000000..0fe7360
--- /dev/null
+++ b/test/Anexia.MathematicalProgram.Tests/Factory/ConstraintFactory.cs
@@ -0,0 +1,20 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Anexia.MathematicalProgram.Model;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Tests.Factory;
+
+internal static class ConstraintFactory
+{
+ public static Constraint Constraint(Term[] terms, IInterval interval) => new(new Terms(terms), interval);
+
+ public static Constraints Constraints(params Constraint[] constraints) => new(constraints);
+}
\ No newline at end of file
diff --git a/test/Anexia.MathematicalProgram.Tests/Factory/IntervalFactory.cs b/test/Anexia.MathematicalProgram.Tests/Factory/IntervalFactory.cs
new file mode 100644
index 0000000..f633689
--- /dev/null
+++ b/test/Anexia.MathematicalProgram.Tests/Factory/IntervalFactory.cs
@@ -0,0 +1,20 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Anexia.MathematicalProgram.Model;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Tests.Factory;
+
+internal static class IntervalFactory
+{
+ public static Interval Interval(double left, double right) => new(new LowerBound(left), new UpperBound(right));
+
+ public static Point Point(double point) => new(point);
+}
\ No newline at end of file
diff --git a/test/Anexia.MathematicalProgram.Tests/Factory/TermFactory.cs b/test/Anexia.MathematicalProgram.Tests/Factory/TermFactory.cs
new file mode 100644
index 0000000..86d3569
--- /dev/null
+++ b/test/Anexia.MathematicalProgram.Tests/Factory/TermFactory.cs
@@ -0,0 +1,19 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Anexia.MathematicalProgram.Model;
+using Google.OrTools.LinearSolver;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Tests.Factory;
+
+internal static class TermFactory
+{
+ public static Term Term(double coefficient, Variable variable) => new(new Coefficient(coefficient), variable);
+}
\ No newline at end of file
diff --git a/test/Anexia.MathematicalProgram.Tests/Model/ConstantTest.cs b/test/Anexia.MathematicalProgram.Tests/Model/ConstantTest.cs
new file mode 100644
index 0000000..1f1416b
--- /dev/null
+++ b/test/Anexia.MathematicalProgram.Tests/Model/ConstantTest.cs
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Anexia.MathematicalProgram.Model;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Tests.Model;
+
+public sealed class ConstantTest
+{
+ [Theory]
+ [InlineData(5, 5, 10)]
+ [InlineData(17, -7, 10)]
+ [InlineData(-7, -7, -14)]
+ [InlineData(double.MaxValue, -5, double.MaxValue)]
+ [InlineData(double.MaxValue, -double.MaxValue, 0)]
+ [InlineData(double.MaxValue, 5, double.MaxValue)]
+ [InlineData(double.MinValue, -7, double.MinValue)]
+ public void ConstantAdditionReturnsCorrectResult(double constant, double toAdd, double result) =>
+ Assert.Equal(new Constant(constant) + toAdd, new Constant(result));
+}
\ No newline at end of file
diff --git a/test/Anexia.MathematicalProgram.Tests/Model/ConstraintsComparisonTest.cs b/test/Anexia.MathematicalProgram.Tests/Model/ConstraintsComparisonTest.cs
new file mode 100644
index 0000000..8f90073
--- /dev/null
+++ b/test/Anexia.MathematicalProgram.Tests/Model/ConstraintsComparisonTest.cs
@@ -0,0 +1,60 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Anexia.MathematicalProgram.Model;
+using Anexia.MathematicalProgram.Solve;
+using Anexia.MathematicalProgram.SolverConfiguration;
+using Anexia.MathematicalProgram.Tests.Factory;
+using static Anexia.MathematicalProgram.Tests.Factory.ConstraintFactory;
+using static Anexia.MathematicalProgram.Tests.Factory.TermFactory;
+#endregion
+
+namespace Anexia.MathematicalProgram.Tests.Model;
+
+public sealed class ConstraintsComparisonTest
+{
+ [Fact]
+ public void ConstraintsWithDifferentTermsOrderMatch()
+ {
+ var solver = IntegerLinearProgramSolver.Create(IlpSolverType.CbcMixedIntegerProgramming, out _);
+
+ _ = solver.AddIntegerVariable(IntervalFactory.Interval(0, 1), "1", out var variable1);
+ _ = solver.AddIntegerVariable(IntervalFactory.Interval(0, 1), "2", out var variable2);
+ _ = solver.AddIntegerVariable(IntervalFactory.Interval(0, 1), "3", out var variable3);
+
+ var constraints = Constraints(Constraint([Term(1, variable1)], Point.One),
+ Constraint([Term(1, variable2), Term(1, variable3)], Point.One),
+ Constraint([Term(1, variable1), Term(1, variable3)], Point.One));
+
+ var constraintDifferentTermsOrder = Constraints(Constraint([Term(1, variable1)], Point.One),
+ Constraint([Term(1, variable3), Term(1, variable2)], Point.One),
+ Constraint([Term(1, variable3), Term(1, variable1)], Point.One));
+
+ Assert.Equal(constraintDifferentTermsOrder, constraints);
+ }
+
+ [Fact]
+ public void ConstraintsWithDifferentConstraintOrderDoNotOrderMatch()
+ {
+ var solver = IntegerLinearProgramSolver.Create(IlpSolverType.CbcMixedIntegerProgramming, out _);
+
+ _ = solver.AddIntegerVariable(IntervalFactory.Interval(0, 1), "1", out var variable1);
+ _ = solver.AddIntegerVariable(IntervalFactory.Interval(0, 1), "2", out var variable2);
+ _ = solver.AddIntegerVariable(IntervalFactory.Interval(0, 1), "3", out var variable3);
+
+ var constraints = Constraints(Constraint([Term(1, variable1)], Point.One),
+ Constraint([Term(1, variable2), Term(1, variable3)], Point.One),
+ Constraint([Term(1, variable1), Term(1, variable3)], Point.One));
+
+ var constraintDifferentTermsOrder = Constraints(Constraint([Term(1, variable1)], Point.One),
+ Constraint([Term(1, variable3), Term(1, variable1)], Point.One),
+ Constraint([Term(1, variable3), Term(1, variable2)], Point.One));
+
+ Assert.NotEqual(constraintDifferentTermsOrder, constraints);
+ }
+}
\ No newline at end of file
diff --git a/test/Anexia.MathematicalProgram.Tests/Model/IntervalTest.cs b/test/Anexia.MathematicalProgram.Tests/Model/IntervalTest.cs
new file mode 100644
index 0000000..d02b058
--- /dev/null
+++ b/test/Anexia.MathematicalProgram.Tests/Model/IntervalTest.cs
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Anexia.MathematicalProgram.Model;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Tests.Model;
+
+public sealed class IntervalTest
+{
+ [Theory]
+ [InlineData(6, 5)]
+ [InlineData(0.1, 0)]
+ [InlineData(-0.1, -0.11)]
+ [InlineData(double.MaxValue, double.Epsilon)]
+ public void IntervalInitializingThrowsExpectedException(double left, double right) =>
+ Assert.Throws(() => new Interval(new LowerBound(left), new UpperBound(right)));
+
+ [Fact]
+ public void SuccessfulIntervalInitializing() => Assert.NotNull(new Interval(new LowerBound(5), new UpperBound(6)));
+}
\ No newline at end of file
diff --git a/test/Anexia.MathematicalProgram.Tests/Solve/IntegerLinearSolverTest.cs b/test/Anexia.MathematicalProgram.Tests/Solve/IntegerLinearSolverTest.cs
new file mode 100644
index 0000000..075bfc1
--- /dev/null
+++ b/test/Anexia.MathematicalProgram.Tests/Solve/IntegerLinearSolverTest.cs
@@ -0,0 +1,84 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Anexia.MathematicalProgram.Model;
+using Anexia.MathematicalProgram.Result;
+using Anexia.MathematicalProgram.Solve;
+using Anexia.MathematicalProgram.SolverConfiguration;
+using static Anexia.MathematicalProgram.Tests.Factory.ConstraintFactory;
+using static Anexia.MathematicalProgram.Tests.Factory.IntervalFactory;
+using static Anexia.MathematicalProgram.Tests.Factory.TermFactory;
+using Interval = Anexia.MathematicalProgram.Model.Interval;
+using Point = Anexia.MathematicalProgram.Model.Point;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Tests.Solve;
+
+public sealed class IntegerLinearSolverTest
+{
+ [Fact]
+ public void SolverWithoutObjectiveAndConstraintsReturnsCorrectResult()
+ {
+ var result = new IntegerLinearProgramSolver().Solve();
+
+ Assert.Equal(
+ new SolverResult(result.SolvedSolver, new ObjectiveValue(0), new IsFeasible(true), new IsOptimal(true),
+ new OptimalityGap(0)), result);
+ }
+
+ [Fact]
+ public void SolverWithSimpleFeasibleIlpModelReturnsCorrectResult()
+ {
+ /*
+ * min 2x, s.t. x=1, x binary
+ */
+ var result = new IntegerLinearProgramSolver()
+ .AddIntegerVariable(Interval.BinaryInterval, "TestVariable", out var testVariable)
+ .SetObjective(new Terms(Term(2, testVariable)), true)
+ .AddConstraints(Constraints(Constraint([Term(1, testVariable)], Point.One))).Solve();
+
+ Assert.Equal(new SolverResult(
+ result.SolvedSolver, new ObjectiveValue(2), new IsFeasible(true), new IsOptimal(true),
+ new OptimalityGap(0)), result);
+ }
+
+ [Fact]
+ public void SolverWithInfeasibleIlModelReturnsCorrectResult()
+ {
+ /*
+ * max 2x, s.t. x=3, x binary
+ */
+ var result = new IntegerLinearProgramSolver()
+ .AddIntegerVariable(Interval.BinaryInterval, "TestVariable", out var testVariable)
+ .SetObjective(new Terms(Term(2, testVariable)), false)
+ .AddConstraints(Constraints(Constraint([Term(1, testVariable)], Point(3)))).Solve(new SolverParameter(
+ EnableSolverOutput.True,
+ RelativeGap.EMinus7,
+ new TimeLimitInMilliseconds(10),
+ new NumberOfThreads(2)));
+
+ Assert.Equal(new SolverResult(
+ result.SolvedSolver, new ObjectiveValue(double.NaN), new IsFeasible(false), new IsOptimal(false),
+ new OptimalityGap(double.NaN)), result);
+ }
+
+ [Fact]
+ public void SolverWithUnboundedIlModelThrowsExpectedException()
+ {
+ /*
+ * max 2x, x positive
+ */
+ var solver = new IntegerLinearProgramSolver()
+ .AddIntegerVariable(Interval(0, double.PositiveInfinity), "TestVariable", out var testVariable)
+ .SetObjective(new Terms(Term(2, testVariable)), false);
+
+ Assert.Throws(() =>
+ solver.Solve(new SolverParameter(new TimeLimitInMilliseconds(10))));
+ }
+}
\ No newline at end of file
diff --git a/test/Anexia.MathematicalProgram.Tests/Solve/LinearSolverTest.cs b/test/Anexia.MathematicalProgram.Tests/Solve/LinearSolverTest.cs
new file mode 100644
index 0000000..b284243
--- /dev/null
+++ b/test/Anexia.MathematicalProgram.Tests/Solve/LinearSolverTest.cs
@@ -0,0 +1,98 @@
+// ------------------------------------------------------------------------------------------
+//
+// Copyright (c) ANEXIA® Internetdienstleistungs GmbH.All rights reserved.
+//
+// ------------------------------------------------------------------------------------------
+
+#region
+
+using Anexia.MathematicalProgram.Model;
+using Anexia.MathematicalProgram.Result;
+using Anexia.MathematicalProgram.Solve;
+using Anexia.MathematicalProgram.SolverConfiguration;
+using static System.Double;
+using static Anexia.MathematicalProgram.Tests.Factory.ConstraintFactory;
+using static Anexia.MathematicalProgram.Tests.Factory.IntervalFactory;
+using static Anexia.MathematicalProgram.Tests.Factory.TermFactory;
+
+#endregion
+
+namespace Anexia.MathematicalProgram.Tests.Solve;
+
+public sealed class LinearSolverTest
+{
+ [Fact]
+ public void SolverWithoutObjectiveAndConstraintsReturnsCorrectResult()
+ {
+ var result = new LinearProgramSolver().Solve();
+
+ Assert.Equal(new SolverResult(
+ result.SolvedSolver, new ObjectiveValue(0), new IsFeasible(true), new IsOptimal(true),
+ new OptimalityGap(0)), result);
+ }
+
+ [Fact]
+ public void SolverWithSimpleFeasibleMinimizationLpModelReturnsCorrectResult()
+ {
+ /*
+ * min 2x, s.t. x=2, x in (0,3)
+ */
+ var result = new LinearProgramSolver()
+ .AddContinuousVariable(Interval(0, 3), "TestVariable", out var testVariable)
+ .SetObjective(new Terms(Term(2, testVariable)), true)
+ .AddConstraints(Constraints(Constraint([Term(1, testVariable)], Point(2)))).Solve();
+
+ Assert.Equal(new SolverResult(
+ result.SolvedSolver, new ObjectiveValue(4), new IsFeasible(true), new IsOptimal(true),
+ new OptimalityGap(0)), result);
+ }
+
+ [Fact]
+ public void SolverWithSimpleFeasibleMaximizationLpModelReturnsCorrectResult()
+ {
+ /*
+ * max 2x, s.t. x<=2, x in (0,3)
+ */
+ var result = new LinearProgramSolver()
+ .AddContinuousVariable(Interval(0, 3), "TestVariable", out var testVariable)
+ .SetObjective(new Terms(Term(2, testVariable)), false)
+ .AddConstraints(Constraints(Constraint([Term(1, testVariable)], Interval(NegativeInfinity, 2)))).Solve();
+
+ Assert.Equal(new SolverResult(
+ result.SolvedSolver, new ObjectiveValue(4), new IsFeasible(true), new IsOptimal(true),
+ new OptimalityGap(0)), result);
+ }
+
+ [Fact]
+ public void SolverWithInfeasibleLpModelReturnsCorrectResult()
+ {
+ /*
+ * max 2x, s.t. x=3, x in (0,1)
+ */
+ var result = new LinearProgramSolver()
+ .AddContinuousVariable(Interval(0, 1), "TestVariable", out var testVariable)
+ .SetObjective(new Terms(Term(2, testVariable)), false)
+ .AddConstraints(Constraints(Constraint([Term(1, testVariable)], Point(3)))).Solve(new SolverParameter(
+ EnableSolverOutput.True,
+ RelativeGap.EMinus7, new TimeLimitInMilliseconds(10),
+ new NumberOfThreads(2)));
+
+ Assert.Equal(new SolverResult(
+ result.SolvedSolver, new ObjectiveValue(double.NaN), new IsFeasible(false), new IsOptimal(false),
+ new OptimalityGap(double.NaN)), result);
+ }
+
+ [Fact]
+ public void SolverWithAbnormalLpModelThrowsExpectedException()
+ {
+ /*
+ * min infinity x, x in R
+ */
+ var solver = new LinearProgramSolver()
+ .AddContinuousVariable(Interval(NegativeInfinity, PositiveInfinity), "TestVariable", out var testVariable)
+ .SetObjective(new Terms(Term(NegativeInfinity, testVariable)), true);
+
+ Assert.Throws(() =>
+ solver.Solve(new SolverParameter(new TimeLimitInMilliseconds(10))));
+ }
+}
\ No newline at end of file