Skip to content

Commit

Permalink
Merge pull request #70 from ducktordanny/feat/assignment-problem-ui
Browse files Browse the repository at this point in the history
Feat: Assignment problem UI
  • Loading branch information
ducktordanny authored Feb 11, 2023
2 parents f9ac3f2 + 33168d0 commit e659e68
Show file tree
Hide file tree
Showing 94 changed files with 4,601 additions and 856 deletions.
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"bracketSameLine": false,
"bracketSpacing": false,
"printWidth": 120,
"printWidth": 100,
"tabWidth": 2,
"trailingComma": "all",
"semi": true,
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

> All notable changes to this project will be documented in this file.
## [0.8.0] - 2023-02-11

- Add full UI for assignment problem page
- Finish API for assignment problem

## [0.7.1] - 2022-11-06

- Use select instead of inputs in transportation problem tabs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Body, Controller, Param, Post, Query, UseGuards} from '@nestjs/common';

import {AssignmentProblemType, Table, ZeroFindingMethod} from '@opres/shared/types';
import {AssignmentProblemType, ProblemTable, ZeroFindingMethod} from '@opres/shared/types';

import {AssignmentProblemGuard} from './guards/assignment-problem.guard';
import {HungarianMethodService} from './services/hungarian-method.service';
Expand All @@ -18,7 +18,7 @@ export class AssignmentProblemController {

@Post()
public getFullResult(
@Body() assignmentTable: Table,
@Body() assignmentTable: ProblemTable,
@Query('zero-finding-method') zeroFindingMethod: ZeroFindingMethod,
@Query('type') problemType: AssignmentProblemType,
) {
Expand All @@ -30,20 +30,29 @@ export class AssignmentProblemController {
}

@Post('reduce')
public getReducedTable(@Body() assignmentTable: Table, @Query('type') problemType: AssignmentProblemType) {
public getReducedTable(
@Body() assignmentTable: ProblemTable,
@Query('type') problemType: AssignmentProblemType,
) {
return this.reduceService.calculate(assignmentTable, problemType || AssignmentProblemType.Min);
}

@Post('koenig-algorithm')
public getKoenigAlgoResult(
@Body() reducedAssignmentTable: Table,
@Body() reducedAssignmentTable: ProblemTable,
@Query('zero-finding-method') zeroFindingMethod: ZeroFindingMethod,
) {
return this.koenigAlgoService.calculate(reducedAssignmentTable, zeroFindingMethod || ZeroFindingMethod.Greedy);
return this.koenigAlgoService.calculate(
reducedAssignmentTable,
zeroFindingMethod || ZeroFindingMethod.Greedy,
);
}

@Post('independent-zeros/:method')
public getIndependentZeros(@Body() reducedAssignmentTable: Table, @Param('method') method: ZeroFindingMethod) {
public getIndependentZeros(
@Body() reducedAssignmentTable: ProblemTable,
@Param('method') method: ZeroFindingMethod,
) {
return this.koenigAlgoService.findIndependentZeros[method](reducedAssignmentTable);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import {BadRequestException, CanActivate, ExecutionContext, Injectable, NotFoundException} from '@nestjs/common';
import {
BadRequestException,
CanActivate,
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';

import {CalculationMode, Table} from '@opres/shared/types';
import {CalculationMode, ProblemTable} from '@opres/shared/types';
import {isAssignmentTableSizeValid, isTableValid} from '@opres/shared/utils';
import {Request} from 'express';
import {Observable} from 'rxjs';

import {isCalculationModeValid} from '../../transport-problem/utils/calculation-mode-checker.util';

type AssignmentPhasesRequest = Request<unknown, unknown, Table, {mode: CalculationMode}>;
type AssignmentPhasesRequest = Request<unknown, unknown, ProblemTable, {mode: CalculationMode}>;

@Injectable()
export class AssignmentProblemGuard implements CanActivate {
Expand All @@ -18,7 +24,9 @@ export class AssignmentProblemGuard implements CanActivate {
const mode = request?.query?.mode;

if (mode && !isCalculationModeValid(mode))
throw new NotFoundException('Calculation mode not found, try one of these instead: steps, explanations, result');
throw new NotFoundException(
'Calculation mode not found, try one of these instead: steps, explanations, result',
);
if (!isTableValid(assignmentTable))
throw new BadRequestException('Invalid assignment table data! Try another one.');
if (!isAssignmentTableSizeValid(assignmentTable))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {Injectable} from '@nestjs/common';
import {
AssignmentProblemType,
HungarianMethodResponse,
Table,
HungarianMethodTransformation,
ProblemTable,
SelectedCell,
TableLineSelections,
ZeroFindingMethod,
} from '@opres/shared/types';
Expand All @@ -14,55 +16,86 @@ import {ReduceService} from './reduce.service';

@Injectable()
export class HungarianMethodService {
constructor(private koenigAlgorithmService: KoenigAlgorithmService, private reduceService: ReduceService) {}
constructor(
private koenigAlgorithmService: KoenigAlgorithmService,
private reduceService: ReduceService,
) {}

public calculate(
assignmentTable: Table,
assignmentTable: ProblemTable,
zeroFindingMethod: ZeroFindingMethod,
problemType: AssignmentProblemType,
): HungarianMethodResponse {
const reduceResult = this.reduceService.calculate(assignmentTable, problemType);
const process: HungarianMethodResponse = [{reduce: reduceResult}];
const process: HungarianMethodResponse = [
{reduce: reduceResult, currentTable: assignmentTable},
];

let transformTable = cloneDeep(reduceResult.reduce);
let koenigMethodResponse = this.koenigAlgorithmService.calculate(transformTable, zeroFindingMethod);
let currentTable = cloneDeep(reduceResult.reduce);
let koenigMethodResponse = this.koenigAlgorithmService.calculate(
currentTable,
zeroFindingMethod,
);
let strikeThroughs = last(koenigMethodResponse).strikeThroughs;

while (strikeThroughs) {
const epsilon = this.getEpsilon(transformTable, strikeThroughs);
transformTable = this.transformation(transformTable, strikeThroughs, epsilon);
process.push(cloneDeep({epsilon, koenigAlgorithm: koenigMethodResponse, transformation: transformTable}));
koenigMethodResponse = this.koenigAlgorithmService.calculate(transformTable, zeroFindingMethod);
const epsilon = this.getEpsilon(currentTable, strikeThroughs);
const transformation = this.transformation(currentTable, strikeThroughs, epsilon);
process.push(
cloneDeep({
currentTable,
koenigAlgorithm: koenigMethodResponse,
transformation,
}),
);
currentTable = cloneDeep(transformation.outputTable);
koenigMethodResponse = this.koenigAlgorithmService.calculate(currentTable, zeroFindingMethod);
strikeThroughs = last(koenigMethodResponse).strikeThroughs;
}

process.push(cloneDeep({transformation: assignmentTable, koenigAlgorithm: koenigMethodResponse}));
process.push(
cloneDeep({
currentTable,
koenigAlgorithm: koenigMethodResponse,
}),
);
return process;
}

/*
* - Where we don't have any strike-throughs we subtract the cell's value with Epsilon
* - Where we have two strike-throughs (crossing each other) we add Epsilon to the cell's value
*/
private transformation(table: Table, strikeThroughs: TableLineSelections, epsilon: number): Table {
const transformedTable = cloneDeep(table);
private transformation(
table: ProblemTable,
strikeThroughs: TableLineSelections,
epsilon: number,
): HungarianMethodTransformation {
const outputTable = cloneDeep(table);
const transformCells: Array<SelectedCell> = [];

forEach(transformedTable, (row, rowIndex) => {
forEach(outputTable, (row, rowIndex) => {
forEach(row, (cell, columnIndex) => {
const hasRowStrikeThrough = this.hasStrikeThrough(strikeThroughs.rows, rowIndex);
const hasColumnStrikeThrough = this.hasStrikeThrough(strikeThroughs.columns, +columnIndex);
if (hasColumnStrikeThrough && hasRowStrikeThrough) transformedTable[rowIndex][columnIndex] = cell + epsilon;
if (!hasRowStrikeThrough && !hasColumnStrikeThrough) transformedTable[rowIndex][columnIndex] = cell - epsilon;
if (hasColumnStrikeThrough && hasRowStrikeThrough) {
outputTable[rowIndex][columnIndex] = cell + epsilon;
transformCells.push({x: +columnIndex, y: rowIndex, value: epsilon});
}
if (!hasRowStrikeThrough && !hasColumnStrikeThrough) {
outputTable[rowIndex][columnIndex] = cell - epsilon;
transformCells.push({x: +columnIndex, y: rowIndex, value: -epsilon});
}
});
});

return transformedTable;
return {outputTable, transformCells, strikeThroughs, epsilon};
}

/*
* The minimum value of the cells where we don't have strike-throughs. (should be positive number, not 0)
*/
private getEpsilon(table: Table, strikeThroughs: TableLineSelections): number {
private getEpsilon(table: ProblemTable, strikeThroughs: TableLineSelections): number {
let epsilon = Infinity;

forEach(table, (row, rowIndex) => {
Expand Down
Loading

0 comments on commit e659e68

Please sign in to comment.