Skip to content

Commit

Permalink
Merge pull request #69 from ducktordanny/feat/assignment-problem-api
Browse files Browse the repository at this point in the history
Feat: Assignment problem API
  • Loading branch information
ducktordanny authored Jan 24, 2023
2 parents 72261dc + 9acdf12 commit f9ac3f2
Show file tree
Hide file tree
Showing 31 changed files with 2,570 additions and 1,056 deletions.
9 changes: 5 additions & 4 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"trailingComma": "all",
"bracketSameLine": false,
"bracketSpacing": false,
"printWidth": 120,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all",
"semi": true,
"bracketSpacing": false,
"bracketSameLine": false
"singleQuote": true
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,49 @@
import {Body, Controller, Post} from '@nestjs/common';
import {Body, Controller, Param, Post, Query, UseGuards} from '@nestjs/common';

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

import {FirstPhaseService} from './services/first-phase.service';
import {SecondPhaseService} from './services/second-phase.service';
import {AssignmentProblemGuard} from './guards/assignment-problem.guard';
import {HungarianMethodService} from './services/hungarian-method.service';
import {KoenigAlgorithmService} from './services/koenig-algorithm.service';
import {ReduceService} from './services/reduce.service';

@UseGuards(AssignmentProblemGuard)
@Controller('assignment-problem')
export class AssignmentProblemController {
constructor(
private firstPhaseService: FirstPhaseService,
private secondPhaseService: SecondPhaseService,
private reduceService: ReduceService,
private koenigAlgoService: KoenigAlgorithmService,
private hungarianMethodService: HungarianMethodService,
) {}

@Post('first-phase')
public calculateFirstPhase(@Body() assignmentTable: Table): Table {
return this.firstPhaseService.calculate(assignmentTable);
@Post()
public getFullResult(
@Body() assignmentTable: Table,
@Query('zero-finding-method') zeroFindingMethod: ZeroFindingMethod,
@Query('type') problemType: AssignmentProblemType,
) {
return this.hungarianMethodService.calculate(
assignmentTable,
zeroFindingMethod || ZeroFindingMethod.Greedy,
problemType || AssignmentProblemType.Min,
);
}

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

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

@Post('independent-zeros/:method')
public getIndependentZeros(@Body() reducedAssignmentTable: Table, @Param('method') method: ZeroFindingMethod) {
return this.koenigAlgoService.findIndependentZeros[method](reducedAssignmentTable);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {Module} from '@nestjs/common';

import {FirstPhaseService} from './services/first-phase.service';
import {SecondPhaseService} from './services/second-phase.service';
import {HungarianMethodService} from './services/hungarian-method.service';
import {KoenigAlgorithmService} from './services/koenig-algorithm.service';
import {ReduceService} from './services/reduce.service';
import {AssignmentProblemController} from './assignment-problem.controller';

@Module({
controllers: [AssignmentProblemController],
providers: [FirstPhaseService, SecondPhaseService],
providers: [HungarianMethodService, KoenigAlgorithmService, ReduceService],
})
export class AssignmentProblemModule {}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {BadRequestException, CanActivate, ExecutionContext, Injectable, NotFoundException} from '@nestjs/common';

import {CalculationMode, Table} 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}>;

@Injectable()
export class AssignmentProblemGuard implements CanActivate {
public canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest() as AssignmentPhasesRequest;

const assignmentTable = request?.body;
const mode = request?.query?.mode;

if (mode && !isCalculationModeValid(mode))
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))
throw new BadRequestException('Invalid assignment table data! Try another one.');

return true;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {Injectable} from '@nestjs/common';

import {
AssignmentProblemType,
HungarianMethodResponse,
Table,
TableLineSelections,
ZeroFindingMethod,
} from '@opres/shared/types';
import {cloneDeep, forEach, last} from 'lodash';

import {KoenigAlgorithmService} from './koenig-algorithm.service';
import {ReduceService} from './reduce.service';

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

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

let transformTable = cloneDeep(reduceResult.reduce);
let koenigMethodResponse = this.koenigAlgorithmService.calculate(transformTable, 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);
strikeThroughs = last(koenigMethodResponse).strikeThroughs;
}

process.push(cloneDeep({transformation: assignmentTable, 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);

forEach(transformedTable, (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;
});
});

return transformedTable;
}

/*
* 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 {
let epsilon = Infinity;

forEach(table, (row, rowIndex) => {
if (this.hasStrikeThrough(strikeThroughs.rows, rowIndex)) return;
forEach(row, (cell, columnIndex) => {
if (this.hasStrikeThrough(strikeThroughs.columns, +columnIndex)) return;
if (epsilon > cell) epsilon = cell;
});
});

return epsilon;
}

private hasStrikeThrough = (strikeThroughsArray: Array<number>, lineIndex: number): boolean =>
strikeThroughsArray.some((index) => index === lineIndex);
}
Loading

0 comments on commit f9ac3f2

Please sign in to comment.